action_form 0.4.0 → 0.5.1
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 +307 -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/rails/base.rb +1 -1
 - data/lib/action_form/subform.rb +4 -1
 - data/lib/action_form/subforms_collection.rb +1 -0
 - 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: 4d93e3e3b61abdc0ceb51759d16f352819d45ccbe1667998484473934da50ff2
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 5f35ee9cc4f39a5ae05eaa445f3f230deef097089bfc88853acab530241ec0f9
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: cbfb9b48199cda2dc7ca78c368c58ecf3628fe5ef508f37e1dc7bfe4a4d3c9a6506ad210552c20e1575e6c50dfc3bac2d08512e3c4d70e9b296dfd9f2819b708
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: c71f8a0fc1c8aa5967f25d5f56702da3bccfa246ca0eae947d80403b80dcb0d46ce75e0a076b41976750e37bccdf4b1b99eac933eca595a3b3a824293a9b82f4
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -621,6 +621,313 @@ expect(params).to be_invalid 
     | 
|
| 
       621 
621 
     | 
    
         | 
| 
       622 
622 
     | 
    
         
             
            This feature provides a powerful way to customize and extend form definitions while maintaining the declarative nature of ActionForm.
         
     | 
| 
       623 
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 
     | 
    
         
            +
             
     | 
| 
       624 
931 
     | 
    
         
             
            ### Tagging system
         
     | 
| 
       625 
932 
     | 
    
         | 
| 
       626 
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:
         
     | 
    
        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
         
     | 
| 
         @@ -63,7 +63,7 @@ module ActionForm 
     | 
|
| 
       63 
63 
     | 
    
         
             
                  end
         
     | 
| 
       64 
64 
     | 
    
         | 
| 
       65 
65 
     | 
    
         
             
                  def with_params(form_params)
         
     | 
| 
       66 
     | 
    
         
            -
                    self.class.new(model: @namespaced_model, scope: @scope, params: form_params, **html_options)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    self.class.new(model: @namespaced_model, scope: @scope, params: form_params, owner: self, **html_options)
         
     | 
| 
       67 
67 
     | 
    
         
             
                  end
         
     | 
| 
       68 
68 
     | 
    
         | 
| 
       69 
69 
     | 
    
         
             
                  def params_definition(scope: self.scope)
         
     | 
    
        data/lib/action_form/subform.rb
    CHANGED
    
    | 
         @@ -8,6 +8,7 @@ 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
         
     | 
| 
         @@ -23,7 +24,7 @@ module ActionForm 
     | 
|
| 
       23 
24 
     | 
    
         
             
                attr_reader :elements_instances, :tags, :name, :object
         
     | 
| 
       24 
25 
     | 
    
         
             
                attr_accessor :helpers
         
     | 
| 
       25 
26 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
                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
         
     | 
| 
       27 
28 
     | 
    
         
             
                  super()
         
     | 
| 
       28 
29 
     | 
    
         
             
                  @name = name
         
     | 
| 
       29 
30 
     | 
    
         
             
                  @scope = scope
         
     | 
| 
         @@ -31,6 +32,7 @@ module ActionForm 
     | 
|
| 
       31 
32 
     | 
    
         
             
                  @params = params
         
     | 
| 
       32 
33 
     | 
    
         
             
                  @elements_instances = []
         
     | 
| 
       33 
34 
     | 
    
         
             
                  @tags = tags
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @owner = owner
         
     | 
| 
       34 
36 
     | 
    
         
             
                  build_from_object
         
     | 
| 
       35 
37 
     | 
    
         
             
                end
         
     | 
| 
       36 
38 
     | 
    
         | 
| 
         @@ -38,6 +40,7 @@ module ActionForm 
     | 
|
| 
       38 
40 
     | 
    
         
             
                  self.class.elements.each do |element_name, element_definition|
         
     | 
| 
       39 
41 
     | 
    
         
             
                    @elements_instances << element_definition.new(element_name, @params || @object, parent_name: @scope)
         
     | 
| 
       40 
42 
     | 
    
         
             
                    @elements_instances.last.tags.merge!(subform: @name)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @elements_instances.last.owner = self
         
     | 
| 
       41 
44 
     | 
    
         
             
                  end
         
     | 
| 
       42 
45 
     | 
    
         
             
                end
         
     | 
| 
       43 
46 
     | 
    
         | 
    
        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.1
         
     | 
| 
       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
         
     |