action_form 0.3.0 → 0.5.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 +4 -4
 - data/README.md +696 -0
 - data/lib/action_form/base.rb +9 -4
 - data/lib/action_form/composition.rb +42 -0
 - data/lib/action_form/element.rb +4 -1
 - data/lib/action_form/elements_dsl.rb +17 -0
 - data/lib/action_form/subform.rb +11 -1
 - data/lib/action_form/subforms_collection.rb +10 -3
 - data/lib/action_form/version.rb +1 -1
 - data/lib/action_form.rb +1 -0
 - metadata +2 -1
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 0211bd706504ef28c1045cc72f33a2408b554ca0a2b3e922436491a1865a16b9
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 87c0249ed1b139d6fea7d26fb9e9d2e220cafa3a045b129530e94b9afb8ef545
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 817ea2221b6780ed40c769fd37416fae7e2051256b7084b4d4cb612c60fac7288a4dad38316a9af0eba543bf712e07afce8185f33e1d3768e7172cfd81de8103
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 32a63869d603e1117ac6bacaf4cd526c7127ba23a703343f537507e31e5739ef9bdce5d6701529413f23f0cd9e8794c3d01b5a4d2954070051eecdcd10a7d881
         
     | 
    
        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,681 @@ 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 
     | 
    
         
            +
             
     | 
| 
      
 624 
     | 
    
         
            +
            ### Composition and Owner Delegation
         
     | 
| 
      
 625 
     | 
    
         
            +
             
     | 
| 
      
 626 
     | 
    
         
            +
            ActionForm includes a composition system that allows form components (elements, subforms, and `many` collections) to access methods from their owner (typically the parent form or a custom host object). This promotes code reuse and reduces redundancy by allowing shared logic to be defined in a central location and accessed by multiple form components.
         
     | 
| 
      
 627 
     | 
    
         
            +
             
     | 
| 
      
 628 
     | 
    
         
            +
            #### **How Composition Works**
         
     | 
| 
      
 629 
     | 
    
         
            +
             
     | 
| 
      
 630 
     | 
    
         
            +
            When you create a form, all nested elements and subforms automatically have access to their owner through the `owner` accessor. Methods called on elements or subforms that are not defined locally are automatically delegated up the ownership chain, allowing you to access methods from the parent form or a custom host object.
         
     | 
| 
      
 631 
     | 
    
         
            +
             
     | 
| 
      
 632 
     | 
    
         
            +
            #### **Automatic Ownership Chain**
         
     | 
| 
      
 633 
     | 
    
         
            +
             
     | 
| 
      
 634 
     | 
    
         
            +
            Ownership is automatically established when forms are built:
         
     | 
| 
      
 635 
     | 
    
         
            +
             
     | 
| 
      
 636 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 637 
     | 
    
         
            +
            class ProductForm < ActionForm::Base
         
     | 
| 
      
 638 
     | 
    
         
            +
              element :name do
         
     | 
| 
      
 639 
     | 
    
         
            +
                input(type: :text)
         
     | 
| 
      
 640 
     | 
    
         
            +
                output(type: :string)
         
     | 
| 
      
 641 
     | 
    
         
            +
             
     | 
| 
      
 642 
     | 
    
         
            +
                # This method can call methods on the owner (ProductForm)
         
     | 
| 
      
 643 
     | 
    
         
            +
                def render?
         
     | 
| 
      
 644 
     | 
    
         
            +
                  name_render?  # Delegates to owner.name_render?
         
     | 
| 
      
 645 
     | 
    
         
            +
                end
         
     | 
| 
      
 646 
     | 
    
         
            +
              end
         
     | 
| 
      
 647 
     | 
    
         
            +
             
     | 
| 
      
 648 
     | 
    
         
            +
              many :variants, default: [{}] do
         
     | 
| 
      
 649 
     | 
    
         
            +
                subform do
         
     | 
| 
      
 650 
     | 
    
         
            +
                  element :name do
         
     | 
| 
      
 651 
     | 
    
         
            +
                    input(type: :text)
         
     | 
| 
      
 652 
     | 
    
         
            +
                    output(type: :string)
         
     | 
| 
      
 653 
     | 
    
         
            +
             
     | 
| 
      
 654 
     | 
    
         
            +
                    def render?
         
     | 
| 
      
 655 
     | 
    
         
            +
                      variants_name_render?  # Delegates to owner.variants_name_render?
         
     | 
| 
      
 656 
     | 
    
         
            +
                    end
         
     | 
| 
      
 657 
     | 
    
         
            +
                  end
         
     | 
| 
      
 658 
     | 
    
         
            +
             
     | 
| 
      
 659 
     | 
    
         
            +
                  def render?
         
     | 
| 
      
 660 
     | 
    
         
            +
                    variants_subform_render?  # Delegates to owner.variants_subform_render?
         
     | 
| 
      
 661 
     | 
    
         
            +
                  end
         
     | 
| 
      
 662 
     | 
    
         
            +
                end
         
     | 
| 
      
 663 
     | 
    
         
            +
              end
         
     | 
| 
      
 664 
     | 
    
         
            +
             
     | 
| 
      
 665 
     | 
    
         
            +
              subform :manufacturer, default: {} do
         
     | 
| 
      
 666 
     | 
    
         
            +
                element :name do
         
     | 
| 
      
 667 
     | 
    
         
            +
                  input(type: :text)
         
     | 
| 
      
 668 
     | 
    
         
            +
                  output(type: :string)
         
     | 
| 
      
 669 
     | 
    
         
            +
             
     | 
| 
      
 670 
     | 
    
         
            +
                  def render?
         
     | 
| 
      
 671 
     | 
    
         
            +
                    manufacturer_name_render?  # Delegates to owner.manufacturer_name_render?
         
     | 
| 
      
 672 
     | 
    
         
            +
                  end
         
     | 
| 
      
 673 
     | 
    
         
            +
                end
         
     | 
| 
      
 674 
     | 
    
         
            +
              end
         
     | 
| 
      
 675 
     | 
    
         
            +
             
     | 
| 
      
 676 
     | 
    
         
            +
              # Methods accessible by nested components
         
     | 
| 
      
 677 
     | 
    
         
            +
              def name_render?
         
     | 
| 
      
 678 
     | 
    
         
            +
                true
         
     | 
| 
      
 679 
     | 
    
         
            +
              end
         
     | 
| 
      
 680 
     | 
    
         
            +
             
     | 
| 
      
 681 
     | 
    
         
            +
              def variants_name_render?
         
     | 
| 
      
 682 
     | 
    
         
            +
                true
         
     | 
| 
      
 683 
     | 
    
         
            +
              end
         
     | 
| 
      
 684 
     | 
    
         
            +
             
     | 
| 
      
 685 
     | 
    
         
            +
              def variants_subform_render?
         
     | 
| 
      
 686 
     | 
    
         
            +
                true
         
     | 
| 
      
 687 
     | 
    
         
            +
              end
         
     | 
| 
      
 688 
     | 
    
         
            +
             
     | 
| 
      
 689 
     | 
    
         
            +
              def manufacturer_name_render?
         
     | 
| 
      
 690 
     | 
    
         
            +
                true
         
     | 
| 
      
 691 
     | 
    
         
            +
              end
         
     | 
| 
      
 692 
     | 
    
         
            +
            end
         
     | 
| 
      
 693 
     | 
    
         
            +
            ```
         
     | 
| 
      
 694 
     | 
    
         
            +
             
     | 
| 
      
 695 
     | 
    
         
            +
            #### **Custom Host Object**
         
     | 
| 
      
 696 
     | 
    
         
            +
             
     | 
| 
      
 697 
     | 
    
         
            +
            You can pass a custom host object when initializing a form, allowing you to separate form logic from business logic:
         
     | 
| 
      
 698 
     | 
    
         
            +
             
     | 
| 
      
 699 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 700 
     | 
    
         
            +
            class HostObject
         
     | 
| 
      
 701 
     | 
    
         
            +
              def name_render?
         
     | 
| 
      
 702 
     | 
    
         
            +
                current_user.admin? || form_context == :edit
         
     | 
| 
      
 703 
     | 
    
         
            +
              end
         
     | 
| 
      
 704 
     | 
    
         
            +
             
     | 
| 
      
 705 
     | 
    
         
            +
              def variants_subform_render?
         
     | 
| 
      
 706 
     | 
    
         
            +
                feature_enabled?(:variants)
         
     | 
| 
      
 707 
     | 
    
         
            +
              end
         
     | 
| 
      
 708 
     | 
    
         
            +
             
     | 
| 
      
 709 
     | 
    
         
            +
              def variants_name_render?
         
     | 
| 
      
 710 
     | 
    
         
            +
                true
         
     | 
| 
      
 711 
     | 
    
         
            +
              end
         
     | 
| 
      
 712 
     | 
    
         
            +
             
     | 
| 
      
 713 
     | 
    
         
            +
              def variants_price_render?
         
     | 
| 
      
 714 
     | 
    
         
            +
                pricing_enabled?
         
     | 
| 
      
 715 
     | 
    
         
            +
              end
         
     | 
| 
      
 716 
     | 
    
         
            +
             
     | 
| 
      
 717 
     | 
    
         
            +
              def manufacturer_name_render?
         
     | 
| 
      
 718 
     | 
    
         
            +
                manufacturer_feature_enabled?
         
     | 
| 
      
 719 
     | 
    
         
            +
              end
         
     | 
| 
      
 720 
     | 
    
         
            +
            end
         
     | 
| 
      
 721 
     | 
    
         
            +
             
     | 
| 
      
 722 
     | 
    
         
            +
            class ProductForm < ActionForm::Base
         
     | 
| 
      
 723 
     | 
    
         
            +
              element :name do
         
     | 
| 
      
 724 
     | 
    
         
            +
                input(type: :text)
         
     | 
| 
      
 725 
     | 
    
         
            +
                output(type: :string)
         
     | 
| 
      
 726 
     | 
    
         
            +
             
     | 
| 
      
 727 
     | 
    
         
            +
                def render?
         
     | 
| 
      
 728 
     | 
    
         
            +
                  name_render?  # Calls HostObject#name_render?
         
     | 
| 
      
 729 
     | 
    
         
            +
                end
         
     | 
| 
      
 730 
     | 
    
         
            +
              end
         
     | 
| 
      
 731 
     | 
    
         
            +
             
     | 
| 
      
 732 
     | 
    
         
            +
              many :variants, default: [{}] do
         
     | 
| 
      
 733 
     | 
    
         
            +
                subform do
         
     | 
| 
      
 734 
     | 
    
         
            +
                  element :name do
         
     | 
| 
      
 735 
     | 
    
         
            +
                    input(type: :text)
         
     | 
| 
      
 736 
     | 
    
         
            +
                    output(type: :string)
         
     | 
| 
      
 737 
     | 
    
         
            +
             
     | 
| 
      
 738 
     | 
    
         
            +
                    def render?
         
     | 
| 
      
 739 
     | 
    
         
            +
                      variants_name_render?  # Calls HostObject#variants_name_render?
         
     | 
| 
      
 740 
     | 
    
         
            +
                    end
         
     | 
| 
      
 741 
     | 
    
         
            +
                  end
         
     | 
| 
      
 742 
     | 
    
         
            +
             
     | 
| 
      
 743 
     | 
    
         
            +
                  element :price do
         
     | 
| 
      
 744 
     | 
    
         
            +
                    input(type: :number)
         
     | 
| 
      
 745 
     | 
    
         
            +
                    output(type: :float)
         
     | 
| 
      
 746 
     | 
    
         
            +
             
     | 
| 
      
 747 
     | 
    
         
            +
                    def render?
         
     | 
| 
      
 748 
     | 
    
         
            +
                      variants_price_render?  # Calls HostObject#variants_price_render?
         
     | 
| 
      
 749 
     | 
    
         
            +
                    end
         
     | 
| 
      
 750 
     | 
    
         
            +
                  end
         
     | 
| 
      
 751 
     | 
    
         
            +
             
     | 
| 
      
 752 
     | 
    
         
            +
                  def render?
         
     | 
| 
      
 753 
     | 
    
         
            +
                    variants_subform_render?  # Calls HostObject#variants_subform_render?
         
     | 
| 
      
 754 
     | 
    
         
            +
                  end
         
     | 
| 
      
 755 
     | 
    
         
            +
                end
         
     | 
| 
      
 756 
     | 
    
         
            +
              end
         
     | 
| 
      
 757 
     | 
    
         
            +
             
     | 
| 
      
 758 
     | 
    
         
            +
              subform :manufacturer, default: {} do
         
     | 
| 
      
 759 
     | 
    
         
            +
                element :name do
         
     | 
| 
      
 760 
     | 
    
         
            +
                  input(type: :text)
         
     | 
| 
      
 761 
     | 
    
         
            +
                  output(type: :string)
         
     | 
| 
      
 762 
     | 
    
         
            +
             
     | 
| 
      
 763 
     | 
    
         
            +
                  def render?
         
     | 
| 
      
 764 
     | 
    
         
            +
                    manufacturer_name_render?  # Calls HostObject#manufacturer_name_render?
         
     | 
| 
      
 765 
     | 
    
         
            +
                  end
         
     | 
| 
      
 766 
     | 
    
         
            +
                end
         
     | 
| 
      
 767 
     | 
    
         
            +
              end
         
     | 
| 
      
 768 
     | 
    
         
            +
            end
         
     | 
| 
      
 769 
     | 
    
         
            +
             
     | 
| 
      
 770 
     | 
    
         
            +
            # Use the form with a custom host object
         
     | 
| 
      
 771 
     | 
    
         
            +
            host = HostObject.new
         
     | 
| 
      
 772 
     | 
    
         
            +
            product = Product.new(name: "Product 1")
         
     | 
| 
      
 773 
     | 
    
         
            +
            form = ProductForm.new(object: product, owner: host)
         
     | 
| 
      
 774 
     | 
    
         
            +
            ```
         
     | 
| 
      
 775 
     | 
    
         
            +
             
     | 
| 
      
 776 
     | 
    
         
            +
            #### **Ownership Chain Traversal**
         
     | 
| 
      
 777 
     | 
    
         
            +
             
     | 
| 
      
 778 
     | 
    
         
            +
            The composition system supports multi-level ownership chains. When a method is called on an element or subform, it searches up the ownership chain until it finds the method:
         
     | 
| 
      
 779 
     | 
    
         
            +
             
     | 
| 
      
 780 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 781 
     | 
    
         
            +
            class GrandparentForm < ActionForm::Base
         
     | 
| 
      
 782 
     | 
    
         
            +
              def shared_helper
         
     | 
| 
      
 783 
     | 
    
         
            +
                "grandparent"
         
     | 
| 
      
 784 
     | 
    
         
            +
              end
         
     | 
| 
      
 785 
     | 
    
         
            +
            end
         
     | 
| 
      
 786 
     | 
    
         
            +
             
     | 
| 
      
 787 
     | 
    
         
            +
            class ParentForm < ActionForm::Base
         
     | 
| 
      
 788 
     | 
    
         
            +
              def shared_helper
         
     | 
| 
      
 789 
     | 
    
         
            +
                "parent"
         
     | 
| 
      
 790 
     | 
    
         
            +
              end
         
     | 
| 
      
 791 
     | 
    
         
            +
            end
         
     | 
| 
      
 792 
     | 
    
         
            +
             
     | 
| 
      
 793 
     | 
    
         
            +
            class ChildForm < ActionForm::Base
         
     | 
| 
      
 794 
     | 
    
         
            +
              element :field do
         
     | 
| 
      
 795 
     | 
    
         
            +
                input(type: :text)
         
     | 
| 
      
 796 
     | 
    
         
            +
             
     | 
| 
      
 797 
     | 
    
         
            +
                def render?
         
     | 
| 
      
 798 
     | 
    
         
            +
                  shared_helper  # Will find ParentForm#shared_helper first
         
     | 
| 
      
 799 
     | 
    
         
            +
                end
         
     | 
| 
      
 800 
     | 
    
         
            +
              end
         
     | 
| 
      
 801 
     | 
    
         
            +
            end
         
     | 
| 
      
 802 
     | 
    
         
            +
             
     | 
| 
      
 803 
     | 
    
         
            +
            # If ChildForm has ParentForm as owner, and ParentForm has GrandparentForm as owner:
         
     | 
| 
      
 804 
     | 
    
         
            +
            # The method lookup order is: ChildForm -> ParentForm -> GrandparentForm
         
     | 
| 
      
 805 
     | 
    
         
            +
            ```
         
     | 
| 
      
 806 
     | 
    
         
            +
             
     | 
| 
      
 807 
     | 
    
         
            +
            #### **Accessing Owner Directly**
         
     | 
| 
      
 808 
     | 
    
         
            +
             
     | 
| 
      
 809 
     | 
    
         
            +
            You can access the owner directly using the `owner` accessor:
         
     | 
| 
      
 810 
     | 
    
         
            +
             
     | 
| 
      
 811 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 812 
     | 
    
         
            +
            class ProductForm < ActionForm::Base
         
     | 
| 
      
 813 
     | 
    
         
            +
              element :discount_code do
         
     | 
| 
      
 814 
     | 
    
         
            +
                input(type: :text)
         
     | 
| 
      
 815 
     | 
    
         
            +
                output(type: :string)
         
     | 
| 
      
 816 
     | 
    
         
            +
             
     | 
| 
      
 817 
     | 
    
         
            +
                def disabled?
         
     | 
| 
      
 818 
     | 
    
         
            +
                  # Access owner's methods directly
         
     | 
| 
      
 819 
     | 
    
         
            +
                  owner.current_user && !owner.current_user.admin?
         
     | 
| 
      
 820 
     | 
    
         
            +
                end
         
     | 
| 
      
 821 
     | 
    
         
            +
             
     | 
| 
      
 822 
     | 
    
         
            +
                def placeholder
         
     | 
| 
      
 823 
     | 
    
         
            +
                  owner.discount_placeholder_text
         
     | 
| 
      
 824 
     | 
    
         
            +
                end
         
     | 
| 
      
 825 
     | 
    
         
            +
              end
         
     | 
| 
      
 826 
     | 
    
         
            +
             
     | 
| 
      
 827 
     | 
    
         
            +
              def current_user
         
     | 
| 
      
 828 
     | 
    
         
            +
                @current_user ||= User.find(session[:user_id])
         
     | 
| 
      
 829 
     | 
    
         
            +
              end
         
     | 
| 
      
 830 
     | 
    
         
            +
             
     | 
| 
      
 831 
     | 
    
         
            +
              def discount_placeholder_text
         
     | 
| 
      
 832 
     | 
    
         
            +
                "Enter discount code"
         
     | 
| 
      
 833 
     | 
    
         
            +
              end
         
     | 
| 
      
 834 
     | 
    
         
            +
            end
         
     | 
| 
      
 835 
     | 
    
         
            +
            ```
         
     | 
| 
      
 836 
     | 
    
         
            +
             
     | 
| 
      
 837 
     | 
    
         
            +
            #### **Ownership Hierarchy**
         
     | 
| 
      
 838 
     | 
    
         
            +
             
     | 
| 
      
 839 
     | 
    
         
            +
            The ownership hierarchy is automatically established as follows:
         
     | 
| 
      
 840 
     | 
    
         
            +
             
     | 
| 
      
 841 
     | 
    
         
            +
            - **Top-level form**: Can have a custom `owner` passed during initialization
         
     | 
| 
      
 842 
     | 
    
         
            +
            - **Elements**: Owner is the form or subform that contains them
         
     | 
| 
      
 843 
     | 
    
         
            +
            - **Subforms**: Owner is the form that contains them
         
     | 
| 
      
 844 
     | 
    
         
            +
            - **SubformsCollection (many)**: Owner is the form that contains them
         
     | 
| 
      
 845 
     | 
    
         
            +
            - **Nested elements in subforms**: Owner is the subform that contains them
         
     | 
| 
      
 846 
     | 
    
         
            +
            - **Nested subforms**: Owner is the parent form or subform
         
     | 
| 
      
 847 
     | 
    
         
            +
             
     | 
| 
      
 848 
     | 
    
         
            +
            #### **Practical Use Cases**
         
     | 
| 
      
 849 
     | 
    
         
            +
             
     | 
| 
      
 850 
     | 
    
         
            +
            **Conditional Rendering Based on Context:**
         
     | 
| 
      
 851 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 852 
     | 
    
         
            +
            class OrderForm < ActionForm::Base
         
     | 
| 
      
 853 
     | 
    
         
            +
              element :admin_notes do
         
     | 
| 
      
 854 
     | 
    
         
            +
                input(type: :textarea)
         
     | 
| 
      
 855 
     | 
    
         
            +
                output(type: :string)
         
     | 
| 
      
 856 
     | 
    
         
            +
             
     | 
| 
      
 857 
     | 
    
         
            +
                def render?
         
     | 
| 
      
 858 
     | 
    
         
            +
                  owner.current_user&.admin?
         
     | 
| 
      
 859 
     | 
    
         
            +
                end
         
     | 
| 
      
 860 
     | 
    
         
            +
              end
         
     | 
| 
      
 861 
     | 
    
         
            +
             
     | 
| 
      
 862 
     | 
    
         
            +
              def current_user
         
     | 
| 
      
 863 
     | 
    
         
            +
                @current_user
         
     | 
| 
      
 864 
     | 
    
         
            +
              end
         
     | 
| 
      
 865 
     | 
    
         
            +
            end
         
     | 
| 
      
 866 
     | 
    
         
            +
            ```
         
     | 
| 
      
 867 
     | 
    
         
            +
             
     | 
| 
      
 868 
     | 
    
         
            +
            **Shared Validation Logic:**
         
     | 
| 
      
 869 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 870 
     | 
    
         
            +
            class RegistrationForm < ActionForm::Base
         
     | 
| 
      
 871 
     | 
    
         
            +
              element :email do
         
     | 
| 
      
 872 
     | 
    
         
            +
                input(type: :email)
         
     | 
| 
      
 873 
     | 
    
         
            +
                output(type: :string)
         
     | 
| 
      
 874 
     | 
    
         
            +
             
     | 
| 
      
 875 
     | 
    
         
            +
                def disabled?
         
     | 
| 
      
 876 
     | 
    
         
            +
                  owner.email_locked?
         
     | 
| 
      
 877 
     | 
    
         
            +
                end
         
     | 
| 
      
 878 
     | 
    
         
            +
              end
         
     | 
| 
      
 879 
     | 
    
         
            +
             
     | 
| 
      
 880 
     | 
    
         
            +
              element :email_confirmation do
         
     | 
| 
      
 881 
     | 
    
         
            +
                input(type: :email)
         
     | 
| 
      
 882 
     | 
    
         
            +
                output(type: :string)
         
     | 
| 
      
 883 
     | 
    
         
            +
             
     | 
| 
      
 884 
     | 
    
         
            +
                def disabled?
         
     | 
| 
      
 885 
     | 
    
         
            +
                  owner.email_locked?
         
     | 
| 
      
 886 
     | 
    
         
            +
                end
         
     | 
| 
      
 887 
     | 
    
         
            +
              end
         
     | 
| 
      
 888 
     | 
    
         
            +
             
     | 
| 
      
 889 
     | 
    
         
            +
              def email_locked?
         
     | 
| 
      
 890 
     | 
    
         
            +
                @user.persisted? && @user.email_verified?
         
     | 
| 
      
 891 
     | 
    
         
            +
              end
         
     | 
| 
      
 892 
     | 
    
         
            +
            end
         
     | 
| 
      
 893 
     | 
    
         
            +
            ```
         
     | 
| 
      
 894 
     | 
    
         
            +
             
     | 
| 
      
 895 
     | 
    
         
            +
            **Feature Flags:**
         
     | 
| 
      
 896 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 897 
     | 
    
         
            +
            class SettingsForm < ActionForm::Base
         
     | 
| 
      
 898 
     | 
    
         
            +
              many :advanced_settings do
         
     | 
| 
      
 899 
     | 
    
         
            +
                subform do
         
     | 
| 
      
 900 
     | 
    
         
            +
                  element :feature_flag do
         
     | 
| 
      
 901 
     | 
    
         
            +
                    input(type: :checkbox)
         
     | 
| 
      
 902 
     | 
    
         
            +
                    output(type: :bool)
         
     | 
| 
      
 903 
     | 
    
         
            +
             
     | 
| 
      
 904 
     | 
    
         
            +
                    def render?
         
     | 
| 
      
 905 
     | 
    
         
            +
                      owner.feature_enabled?(:advanced_settings)
         
     | 
| 
      
 906 
     | 
    
         
            +
                    end
         
     | 
| 
      
 907 
     | 
    
         
            +
                  end
         
     | 
| 
      
 908 
     | 
    
         
            +
                end
         
     | 
| 
      
 909 
     | 
    
         
            +
             
     | 
| 
      
 910 
     | 
    
         
            +
                def render?
         
     | 
| 
      
 911 
     | 
    
         
            +
                  owner.feature_enabled?(:advanced_settings)
         
     | 
| 
      
 912 
     | 
    
         
            +
                end
         
     | 
| 
      
 913 
     | 
    
         
            +
              end
         
     | 
| 
      
 914 
     | 
    
         
            +
             
     | 
| 
      
 915 
     | 
    
         
            +
              def feature_enabled?(feature)
         
     | 
| 
      
 916 
     | 
    
         
            +
                FeatureFlags.enabled?(feature, current_user)
         
     | 
| 
      
 917 
     | 
    
         
            +
              end
         
     | 
| 
      
 918 
     | 
    
         
            +
            end
         
     | 
| 
      
 919 
     | 
    
         
            +
            ```
         
     | 
| 
      
 920 
     | 
    
         
            +
             
     | 
| 
      
 921 
     | 
    
         
            +
            #### **Benefits**
         
     | 
| 
      
 922 
     | 
    
         
            +
             
     | 
| 
      
 923 
     | 
    
         
            +
            - **Code Reuse**: Share common logic across multiple form components
         
     | 
| 
      
 924 
     | 
    
         
            +
            - **Separation of Concerns**: Keep business logic in host objects, form logic in forms
         
     | 
| 
      
 925 
     | 
    
         
            +
            - **Flexibility**: Support conditional rendering and behavior based on context
         
     | 
| 
      
 926 
     | 
    
         
            +
            - **Maintainability**: Centralize shared logic instead of duplicating it
         
     | 
| 
      
 927 
     | 
    
         
            +
            - **Testability**: Test host objects separately from form definitions
         
     | 
| 
      
 928 
     | 
    
         
            +
             
     | 
| 
      
 929 
     | 
    
         
            +
            The composition system provides a powerful mechanism for creating flexible, maintainable forms that can adapt to different contexts and requirements.
         
     | 
| 
      
 930 
     | 
    
         
            +
             
     | 
| 
       253 
931 
     | 
    
         
             
            ### Tagging system
         
     | 
| 
       254 
932 
     | 
    
         | 
| 
       255 
933 
     | 
    
         
             
            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 +1478,22 @@ class UserForm < ActionForm::Rails::Base 
     | 
|
| 
       800 
1478 
     | 
    
         
             
                input type: :email
         
     | 
| 
       801 
1479 
     | 
    
         
             
                output type: :string, presence: true
         
     | 
| 
       802 
1480 
     | 
    
         
             
              end
         
     | 
| 
      
 1481 
     | 
    
         
            +
             
     | 
| 
      
 1482 
     | 
    
         
            +
              element :password do
         
     | 
| 
      
 1483 
     | 
    
         
            +
                input type: :password
         
     | 
| 
      
 1484 
     | 
    
         
            +
                output type: :string
         
     | 
| 
      
 1485 
     | 
    
         
            +
              end
         
     | 
| 
      
 1486 
     | 
    
         
            +
             
     | 
| 
      
 1487 
     | 
    
         
            +
              element :password_confirmation do
         
     | 
| 
      
 1488 
     | 
    
         
            +
                input type: :password
         
     | 
| 
      
 1489 
     | 
    
         
            +
                output type: :string
         
     | 
| 
      
 1490 
     | 
    
         
            +
              end
         
     | 
| 
      
 1491 
     | 
    
         
            +
             
     | 
| 
      
 1492 
     | 
    
         
            +
              # Custom parameter validation for Rails integration
         
     | 
| 
      
 1493 
     | 
    
         
            +
              params do
         
     | 
| 
      
 1494 
     | 
    
         
            +
                validates :password, presence: true, length: { minimum: 6 }
         
     | 
| 
      
 1495 
     | 
    
         
            +
                validates :password, confirmation: true
         
     | 
| 
      
 1496 
     | 
    
         
            +
              end
         
     | 
| 
       803 
1497 
     | 
    
         
             
            end
         
     | 
| 
       804 
1498 
     | 
    
         
             
            ```
         
     | 
| 
       805 
1499 
     | 
    
         | 
| 
         @@ -910,6 +1604,7 @@ class UsersController < ApplicationController 
     | 
|
| 
       910 
1604 
     | 
    
         
             
                  @user = User.create!(user_params.user.to_h)
         
     | 
| 
       911 
1605 
     | 
    
         
             
                  redirect_to @user
         
     | 
| 
       912 
1606 
     | 
    
         
             
                else
         
     | 
| 
      
 1607 
     | 
    
         
            +
                  # Custom validation errors are automatically available
         
     | 
| 
       913 
1608 
     | 
    
         
             
                  @form = @form.with_params(user_params)
         
     | 
| 
       914 
1609 
     | 
    
         
             
                  render :new
         
     | 
| 
       915 
1610 
     | 
    
         
             
                end
         
     | 
| 
         @@ -927,6 +1622,7 @@ class UsersController < ApplicationController 
     | 
|
| 
       927 
1622 
     | 
    
         
             
                  @user.update!(user_params.user.to_h)
         
     | 
| 
       928 
1623 
     | 
    
         
             
                  redirect_to @user
         
     | 
| 
       929 
1624 
     | 
    
         
             
                else
         
     | 
| 
      
 1625 
     | 
    
         
            +
                  # Custom validation errors (like password confirmation) are displayed
         
     | 
| 
       930 
1626 
     | 
    
         
             
                  @form = @form.with_params(user_params)
         
     | 
| 
       931 
1627 
     | 
    
         
             
                  render :edit
         
     | 
| 
       932 
1628 
     | 
    
         
             
                end
         
     | 
    
        data/lib/action_form/base.rb
    CHANGED
    
    | 
         @@ -7,6 +7,7 @@ module ActionForm 
     | 
|
| 
       7 
7 
     | 
    
         
             
                include ActionForm::SchemaDSL
         
     | 
| 
       8 
8 
     | 
    
         
             
                include ActionForm::ElementsDSL
         
     | 
| 
       9 
9 
     | 
    
         
             
                include ActionForm::Rendering
         
     | 
| 
      
 10 
     | 
    
         
            +
                include ActionForm::Composition
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
12 
     | 
    
         
             
                attr_reader :elements_instances, :scope, :object, :html_options, :errors
         
     | 
| 
       12 
13 
     | 
    
         | 
| 
         @@ -30,13 +31,14 @@ module ActionForm 
     | 
|
| 
       30 
31 
     | 
    
         
             
                  end
         
     | 
| 
       31 
32 
     | 
    
         
             
                end
         
     | 
| 
       32 
33 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                def initialize(object: nil, scope: self.class.scope, params: nil, **html_options)
         
     | 
| 
      
 34 
     | 
    
         
            +
                def initialize(object: nil, scope: self.class.scope, params: nil, owner: nil, **html_options)
         
     | 
| 
       34 
35 
     | 
    
         
             
                  super()
         
     | 
| 
       35 
36 
     | 
    
         
             
                  @object = object
         
     | 
| 
       36 
37 
     | 
    
         
             
                  @scope ||= scope
         
     | 
| 
       37 
38 
     | 
    
         
             
                  @params = @scope && params.respond_to?(@scope) ? params.public_send(@scope) : params
         
     | 
| 
       38 
39 
     | 
    
         
             
                  @html_options = html_options
         
     | 
| 
       39 
40 
     | 
    
         
             
                  @elements_instances = []
         
     | 
| 
      
 41 
     | 
    
         
            +
                  @owner = owner
         
     | 
| 
       40 
42 
     | 
    
         
             
                  build_from_object
         
     | 
| 
       41 
43 
     | 
    
         
             
                end
         
     | 
| 
       42 
44 
     | 
    
         | 
| 
         @@ -48,7 +50,7 @@ module ActionForm 
     | 
|
| 
       48 
50 
     | 
    
         
             
                    elsif element_definition < ActionForm::Subform
         
     | 
| 
       49 
51 
     | 
    
         
             
                      @elements_instances << build_subform(name, element_definition)
         
     | 
| 
       50 
52 
     | 
    
         
             
                    elsif element_definition < ActionForm::Element
         
     | 
| 
       51 
     | 
    
         
            -
                      @elements_instances << element_definition.new(name, @params || @object, parent_name: @scope)
         
     | 
| 
      
 53 
     | 
    
         
            +
                      @elements_instances << element_definition.new(name, @params || @object, parent_name: @scope, owner: self)
         
     | 
| 
       52 
54 
     | 
    
         
             
                    end
         
     | 
| 
       53 
55 
     | 
    
         
             
                  end
         
     | 
| 
       54 
56 
     | 
    
         
             
                end
         
     | 
| 
         @@ -73,6 +75,7 @@ module ActionForm 
     | 
|
| 
       73 
75 
     | 
    
         | 
| 
       74 
76 
     | 
    
         
             
                def build_many_subforms(name, collection_definition)
         
     | 
| 
       75 
77 
     | 
    
         
             
                  collection = collection_definition.new(name)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  collection.owner = self
         
     | 
| 
       76 
79 
     | 
    
         
             
                  Array(subform_value(name)).each.with_index do |item, index|
         
     | 
| 
       77 
80 
     | 
    
         
             
                    collection << build_subform(name, collection_definition.subform_definition, value: item, index: index)
         
     | 
| 
       78 
81 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -93,7 +96,8 @@ module ActionForm 
     | 
|
| 
       93 
96 
     | 
    
         | 
| 
       94 
97 
     | 
    
         
             
                def build_subform(name, form_definition, value: subform_value(name), index: nil)
         
     | 
| 
       95 
98 
     | 
    
         
             
                  html_name = subform_html_name(name, index: index)
         
     | 
| 
       96 
     | 
    
         
            -
                  form_definition.new(name: name, scope: html_name, model: value, index: index 
     | 
| 
      
 99 
     | 
    
         
            +
                  form_definition.new(name: name, scope: html_name, model: value, index: index,
         
     | 
| 
      
 100 
     | 
    
         
            +
                                      owner: self).tap do |subform|
         
     | 
| 
       97 
101 
     | 
    
         
             
                    subform.helpers = helpers
         
     | 
| 
       98 
102 
     | 
    
         
             
                  end
         
     | 
| 
       99 
103 
     | 
    
         
             
                end
         
     | 
| 
         @@ -103,7 +107,8 @@ module ActionForm 
     | 
|
| 
       103 
107 
     | 
    
         
             
                  elements_keys = form_definition.elements.keys.push(:persisted?)
         
     | 
| 
       104 
108 
     | 
    
         
             
                  values = form_definition.elements.values.map(&:default)
         
     | 
| 
       105 
109 
     | 
    
         
             
                  value = Struct.new(*elements_keys).new(*values)
         
     | 
| 
       106 
     | 
    
         
            -
                  form_definition.new(name: name, scope: html_name, model: value, template: true 
     | 
| 
      
 110 
     | 
    
         
            +
                  form_definition.new(name: name, scope: html_name, model: value, template: true,
         
     | 
| 
      
 111 
     | 
    
         
            +
                                      owner: self).tap do |subform|
         
     | 
| 
       107 
112 
     | 
    
         
             
                    subform.helpers = helpers
         
     | 
| 
       108 
113 
     | 
    
         
             
                  end
         
     | 
| 
       109 
114 
     | 
    
         
             
                end
         
     | 
| 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ActionForm
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Provides host–guest association helpers for ActionForm components.
         
     | 
| 
      
 5 
     | 
    
         
            +
              # When included, it exposes `host_object` and `host_association_name` accessors
         
     | 
| 
      
 6 
     | 
    
         
            +
              # and delegates `host_*` method calls to the associated host via `method_missing`.
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Composition
         
     | 
| 
      
 8 
     | 
    
         
            +
                def self.included(base)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  base.attr_accessor :owner
         
     | 
| 
      
 10 
     | 
    
         
            +
                  base.include(InstanceMethods)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  base.extend(ClassMethods)
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                module ClassMethods # rubocop:disable Style/Documentation
         
     | 
| 
      
 15 
     | 
    
         
            +
                  def part_of(owner_name)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @owner_name = owner_name
         
     | 
| 
      
 17 
     | 
    
         
            +
                    alias_method owner_name, :owner
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                module InstanceMethods # rubocop:disable Style/Documentation
         
     | 
| 
      
 22 
     | 
    
         
            +
                  def method_missing(name, *attrs, **kwargs, &block)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    if (handler = owners_chain.lazy.detect { |o| o.public_methods.include?(name) })
         
     | 
| 
      
 24 
     | 
    
         
            +
                      handler.public_send(name, *attrs, **kwargs, &block)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    else
         
     | 
| 
      
 26 
     | 
    
         
            +
                      super
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def respond_to_missing?(method_name, _include_private = false)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    public_methods.detect { |m| m == :owner } || super
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def owners_chain
         
     | 
| 
      
 35 
     | 
    
         
            +
                    obj = self
         
     | 
| 
      
 36 
     | 
    
         
            +
                    Enumerator.new do |y|
         
     | 
| 
      
 37 
     | 
    
         
            +
                      y << obj = obj.owner while obj.public_methods.include?(:owner)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/action_form/element.rb
    CHANGED
    
    | 
         @@ -4,10 +4,12 @@ module ActionForm 
     | 
|
| 
       4 
4 
     | 
    
         
             
              # Represents a form element with input/output configuration and HTML attributes
         
     | 
| 
       5 
5 
     | 
    
         
             
              # rubocop:disable Metrics/ClassLength
         
     | 
| 
       6 
6 
     | 
    
         
             
              class Element
         
     | 
| 
      
 7 
     | 
    
         
            +
                include ActionForm::Composition
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
       7 
9 
     | 
    
         
             
                attr_reader :name, :input_options, :output_options, :html_name, :html_id, :select_options, :tags, :errors_messages
         
     | 
| 
       8 
10 
     | 
    
         
             
                attr_accessor :helpers
         
     | 
| 
       9 
11 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                def initialize(name, object, parent_name: nil)
         
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize(name, object, parent_name: nil, owner: nil)
         
     | 
| 
       11 
13 
     | 
    
         
             
                  @name = name
         
     | 
| 
       12 
14 
     | 
    
         
             
                  @object = object
         
     | 
| 
       13 
15 
     | 
    
         
             
                  @html_name = build_html_name(name, parent_name)
         
     | 
| 
         @@ -15,6 +17,7 @@ module ActionForm 
     | 
|
| 
       15 
17 
     | 
    
         
             
                  @tags = self.class.tags_list.dup
         
     | 
| 
       16 
18 
     | 
    
         
             
                  @errors_messages = extract_errors_messages(object, name)
         
     | 
| 
       17 
19 
     | 
    
         
             
                  tags.merge!(errors: errors_messages.any?)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @owner = owner
         
     | 
| 
       18 
21 
     | 
    
         
             
                end
         
     | 
| 
       19 
22 
     | 
    
         | 
| 
       20 
23 
     | 
    
         
             
                class << self
         
     | 
| 
         @@ -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
         
     | 
    
        data/lib/action_form/subform.rb
    CHANGED
    
    | 
         @@ -8,15 +8,23 @@ module ActionForm 
     | 
|
| 
       8 
8 
     | 
    
         
             
                include ActionForm::Rendering
         
     | 
| 
       9 
9 
     | 
    
         
             
                include ActionForm::SchemaDSL
         
     | 
| 
       10 
10 
     | 
    
         
             
                include ActionForm::ElementsDSL
         
     | 
| 
      
 11 
     | 
    
         
            +
                include ActionForm::Composition
         
     | 
| 
       11 
12 
     | 
    
         | 
| 
       12 
13 
     | 
    
         
             
                class << self
         
     | 
| 
       13 
14 
     | 
    
         
             
                  attr_accessor :default
         
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_writer :elements
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def inherited(subclass)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    super
         
     | 
| 
      
 19 
     | 
    
         
            +
                    subclass.elements = elements.dup
         
     | 
| 
      
 20 
     | 
    
         
            +
                    subclass.default = default.dup
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
       14 
22 
     | 
    
         
             
                end
         
     | 
| 
       15 
23 
     | 
    
         | 
| 
       16 
24 
     | 
    
         
             
                attr_reader :elements_instances, :tags, :name, :object
         
     | 
| 
       17 
25 
     | 
    
         
             
                attr_accessor :helpers
         
     | 
| 
       18 
26 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
                def initialize(name:, scope: nil, model: nil, params: nil, **tags)
         
     | 
| 
      
 27 
     | 
    
         
            +
                def initialize(name:, scope: nil, model: nil, params: nil, owner: nil, **tags) # rubocop:disable Metrics/ParameterLists
         
     | 
| 
       20 
28 
     | 
    
         
             
                  super()
         
     | 
| 
       21 
29 
     | 
    
         
             
                  @name = name
         
     | 
| 
       22 
30 
     | 
    
         
             
                  @scope = scope
         
     | 
| 
         @@ -24,6 +32,7 @@ module ActionForm 
     | 
|
| 
       24 
32 
     | 
    
         
             
                  @params = params
         
     | 
| 
       25 
33 
     | 
    
         
             
                  @elements_instances = []
         
     | 
| 
       26 
34 
     | 
    
         
             
                  @tags = tags
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @owner = owner
         
     | 
| 
       27 
36 
     | 
    
         
             
                  build_from_object
         
     | 
| 
       28 
37 
     | 
    
         
             
                end
         
     | 
| 
       29 
38 
     | 
    
         | 
| 
         @@ -31,6 +40,7 @@ module ActionForm 
     | 
|
| 
       31 
40 
     | 
    
         
             
                  self.class.elements.each do |element_name, element_definition|
         
     | 
| 
       32 
41 
     | 
    
         
             
                    @elements_instances << element_definition.new(element_name, @params || @object, parent_name: @scope)
         
     | 
| 
       33 
42 
     | 
    
         
             
                    @elements_instances.last.tags.merge!(subform: @name)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @elements_instances.last.owner = self
         
     | 
| 
       34 
44 
     | 
    
         
             
                  end
         
     | 
| 
       35 
45 
     | 
    
         
             
                end
         
     | 
| 
       36 
46 
     | 
    
         | 
| 
         @@ -5,6 +5,7 @@ module ActionForm 
     | 
|
| 
       5 
5 
     | 
    
         
             
              class SubformsCollection < ::Phlex::HTML
         
     | 
| 
       6 
6 
     | 
    
         
             
                extend Forwardable
         
     | 
| 
       7 
7 
     | 
    
         
             
                include ActionForm::Rendering
         
     | 
| 
      
 8 
     | 
    
         
            +
                include ActionForm::Composition
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
       9 
10 
     | 
    
         
             
                def_delegators :@subforms, :last, :first, :length, :size, :[], :<<
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
         @@ -12,13 +13,19 @@ module ActionForm 
     | 
|
| 
       12 
13 
     | 
    
         
             
                attr_accessor :helpers
         
     | 
| 
       13 
14 
     | 
    
         | 
| 
       14 
15 
     | 
    
         
             
                class << self
         
     | 
| 
       15 
     | 
    
         
            -
                   
     | 
| 
       16 
     | 
    
         
            -
                  attr_accessor :default, :host_class
         
     | 
| 
      
 16 
     | 
    
         
            +
                  attr_accessor :default, :host_class, :subform_definition
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
                  def subform(subform_class = nil, &block)
         
     | 
| 
       19 
     | 
    
         
            -
                    @subform_definition  
     | 
| 
      
 19 
     | 
    
         
            +
                    @subform_definition ||= subform_class || Class.new(host_class.subform_class)
         
     | 
| 
       20 
20 
     | 
    
         
             
                    @subform_definition.class_eval(&block) if block
         
     | 
| 
       21 
21 
     | 
    
         
             
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def inherited(subclass)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    super
         
     | 
| 
      
 25 
     | 
    
         
            +
                    subclass.subform_definition = subform_definition
         
     | 
| 
      
 26 
     | 
    
         
            +
                    subclass.default = default
         
     | 
| 
      
 27 
     | 
    
         
            +
                    subclass.host_class = host_class
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
       22 
29 
     | 
    
         
             
                end
         
     | 
| 
       23 
30 
     | 
    
         | 
| 
       24 
31 
     | 
    
         
             
                def initialize(name)
         
     | 
    
        data/lib/action_form/version.rb
    CHANGED
    
    
    
        data/lib/action_form.rb
    CHANGED
    
    | 
         @@ -4,6 +4,7 @@ require "phlex" 
     | 
|
| 
       4 
4 
     | 
    
         
             
            require "easy_params"
         
     | 
| 
       5 
5 
     | 
    
         
             
            require "forwardable"
         
     | 
| 
       6 
6 
     | 
    
         
             
            require_relative "action_form/version"
         
     | 
| 
      
 7 
     | 
    
         
            +
            require_relative "action_form/composition"
         
     | 
| 
       7 
8 
     | 
    
         
             
            require_relative "action_form/schema_dsl"
         
     | 
| 
       8 
9 
     | 
    
         
             
            require_relative "action_form/elements_dsl"
         
     | 
| 
       9 
10 
     | 
    
         
             
            require_relative "action_form/input"
         
     | 
    
        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. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.5.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Andrii Baran
         
     | 
| 
         @@ -67,6 +67,7 @@ files: 
     | 
|
| 
       67 
67 
     | 
    
         
             
            - Rakefile
         
     | 
| 
       68 
68 
     | 
    
         
             
            - lib/action_form.rb
         
     | 
| 
       69 
69 
     | 
    
         
             
            - lib/action_form/base.rb
         
     | 
| 
      
 70 
     | 
    
         
            +
            - lib/action_form/composition.rb
         
     | 
| 
       70 
71 
     | 
    
         
             
            - lib/action_form/element.rb
         
     | 
| 
       71 
72 
     | 
    
         
             
            - lib/action_form/elements_dsl.rb
         
     | 
| 
       72 
73 
     | 
    
         
             
            - lib/action_form/input.rb
         
     |