plutonium 0.10.0 → 0.10.2
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 +2 -0
 - data/app/assets/javascripts/turbo/index.js +1 -1
 - data/app/views/application/_resource_header.html.erb +12 -12
 - data/app/views/layouts/resource.html.erb +1 -0
 - data/app/views/layouts/rodauth.html.erb +4 -4
 - data/app/views/resource/_nav_user.html.erb +1 -1
 - data/app/views/resource/index.rabl +1 -1
 - data/brakeman.ignore +1 -1
 - data/config/initializers/rabl.rb +2 -0
 - data/css.manifest +1 -1
 - data/js.manifest +2 -2
 - data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +0 -3
 - data/lib/generators/pu/gen/pug/pug_generator.rb +6 -0
 - data/lib/generators/pu/gen/pug/templates/pug.rb.tt +1 -1
 - data/lib/generators/pu/pkg/app/templates/config/routes.rb.tt +3 -3
 - data/lib/plutonium/config.rb +1 -11
 - data/lib/plutonium/core/autodiscovery/input_discoverer.rb +1 -1
 - data/lib/plutonium/core/autodiscovery/renderer_discoverer.rb +1 -1
 - data/lib/plutonium/core/controllers/base.rb +13 -3
 - data/lib/plutonium/core/controllers/queryable.rb +3 -1
 - data/lib/plutonium/pkg/app.rb +6 -0
 - data/lib/plutonium/railtie.rb +15 -0
 - data/lib/plutonium/reloader.rb +99 -0
 - data/lib/plutonium/resource/controller.rb +61 -22
 - data/lib/plutonium/resource/policy.rb +56 -9
 - data/lib/plutonium/resource/presenter.rb +44 -12
 - data/lib/plutonium/resource/query_object.rb +186 -73
 - data/lib/plutonium/resource/record.rb +213 -119
 - data/lib/plutonium/rodauth/controller_methods.rb +7 -0
 - data/lib/plutonium/version.rb +1 -1
 - data/lib/plutonium.rb +50 -12
 - data/package-lock.json +174 -0
 - data/package.json +2 -0
 - data/public/plutonium-assets/plutonium-app-6WILQCTT.js +39 -0
 - data/public/plutonium-assets/plutonium-app-6WILQCTT.js.map +7 -0
 - data/public/plutonium-assets/plutonium-logo-original.png +0 -0
 - data/public/plutonium-assets/plutonium-logo-white.png +0 -0
 - data/public/plutonium-assets/plutonium-logo.png +0 -0
 - data/public/plutonium-assets/plutonium.2d4f0c333cd000051d3b.css +3424 -0
 - data/public/plutonium-assets/plutonium.ico +0 -0
 - metadata +10 -4
 - data/lib/plutonium/reactor/core.rb +0 -78
 - data/public/plutonium-assets/logo.png +0 -0
 
| 
         @@ -1,108 +1,155 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Plutonium
         
     | 
| 
       2 
2 
     | 
    
         
             
              module Resource
         
     | 
| 
      
 3 
     | 
    
         
            +
                # Policy class to define permissions and attributes for a resource
         
     | 
| 
       3 
4 
     | 
    
         
             
                class Policy
         
     | 
| 
       4 
5 
     | 
    
         
             
                  include Plutonium::Policy::Initializer
         
     | 
| 
       5 
6 
     | 
    
         | 
| 
       6 
7 
     | 
    
         
             
                  class Scope < Plutonium::Policy::Scope
         
     | 
| 
       7 
8 
     | 
    
         
             
                  end
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
      
 10 
     | 
    
         
            +
                  # Sends a method and raises an error if the method is not implemented
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # @param [Symbol] method The method to send
         
     | 
| 
       9 
12 
     | 
    
         
             
                  def send_with_report(method)
         
     | 
| 
       10 
     | 
    
         
            -
                     
     | 
| 
      
 13 
     | 
    
         
            +
                    unless respond_to?(method)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      raise NotImplementedError, "#{self.class.name} does not implement the required #{method}"
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
       11 
16 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
                    send 
     | 
| 
      
 17 
     | 
    
         
            +
                    send(method)
         
     | 
| 
       13 
18 
     | 
    
         
             
                  end
         
     | 
| 
       14 
19 
     | 
    
         | 
| 
       15 
20 
     | 
    
         
             
                  # Core actions
         
     | 
| 
       16 
21 
     | 
    
         | 
| 
      
 22 
     | 
    
         
            +
                  # Checks if the create action is permitted
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # @return [Boolean]
         
     | 
| 
       17 
24 
     | 
    
         
             
                  def create?
         
     | 
| 
       18 
25 
     | 
    
         
             
                    false
         
     | 
| 
       19 
26 
     | 
    
         
             
                  end
         
     | 
| 
       20 
27 
     | 
    
         | 
| 
      
 28 
     | 
    
         
            +
                  # Checks if the read action is permitted
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # @return [Boolean]
         
     | 
| 
       21 
30 
     | 
    
         
             
                  def read?
         
     | 
| 
       22 
31 
     | 
    
         
             
                    false
         
     | 
| 
       23 
32 
     | 
    
         
             
                  end
         
     | 
| 
       24 
33 
     | 
    
         | 
| 
      
 34 
     | 
    
         
            +
                  # Checks if the update action is permitted
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # @return [Boolean]
         
     | 
| 
       25 
36 
     | 
    
         
             
                  def update?
         
     | 
| 
       26 
37 
     | 
    
         
             
                    create?
         
     | 
| 
       27 
38 
     | 
    
         
             
                  end
         
     | 
| 
       28 
39 
     | 
    
         | 
| 
      
 40 
     | 
    
         
            +
                  # Checks if the destroy action is permitted
         
     | 
| 
      
 41 
     | 
    
         
            +
                  # @return [Boolean]
         
     | 
| 
       29 
42 
     | 
    
         
             
                  def destroy?
         
     | 
| 
       30 
43 
     | 
    
         
             
                    create?
         
     | 
| 
       31 
44 
     | 
    
         
             
                  end
         
     | 
| 
       32 
45 
     | 
    
         | 
| 
       33 
46 
     | 
    
         
             
                  # Inferred actions
         
     | 
| 
       34 
47 
     | 
    
         | 
| 
      
 48 
     | 
    
         
            +
                  # Checks if the index action is permitted
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # @return [Boolean]
         
     | 
| 
       35 
50 
     | 
    
         
             
                  def index?
         
     | 
| 
       36 
51 
     | 
    
         
             
                    read?
         
     | 
| 
       37 
52 
     | 
    
         
             
                  end
         
     | 
| 
       38 
53 
     | 
    
         | 
| 
      
 54 
     | 
    
         
            +
                  # Checks if the new action is permitted
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # @return [Boolean]
         
     | 
| 
       39 
56 
     | 
    
         
             
                  def new?
         
     | 
| 
       40 
57 
     | 
    
         
             
                    create?
         
     | 
| 
       41 
58 
     | 
    
         
             
                  end
         
     | 
| 
       42 
59 
     | 
    
         | 
| 
      
 60 
     | 
    
         
            +
                  # Checks if the show action is permitted
         
     | 
| 
      
 61 
     | 
    
         
            +
                  # @return [Boolean]
         
     | 
| 
       43 
62 
     | 
    
         
             
                  def show?
         
     | 
| 
       44 
63 
     | 
    
         
             
                    read?
         
     | 
| 
       45 
64 
     | 
    
         
             
                  end
         
     | 
| 
       46 
65 
     | 
    
         | 
| 
      
 66 
     | 
    
         
            +
                  # Checks if the edit action is permitted
         
     | 
| 
      
 67 
     | 
    
         
            +
                  # @return [Boolean]
         
     | 
| 
       47 
68 
     | 
    
         
             
                  def edit?
         
     | 
| 
       48 
69 
     | 
    
         
             
                    update?
         
     | 
| 
       49 
70 
     | 
    
         
             
                  end
         
     | 
| 
       50 
71 
     | 
    
         | 
| 
      
 72 
     | 
    
         
            +
                  # Checks if the search action is permitted
         
     | 
| 
      
 73 
     | 
    
         
            +
                  # @return [Boolean]
         
     | 
| 
       51 
74 
     | 
    
         
             
                  def search?
         
     | 
| 
       52 
75 
     | 
    
         
             
                    index?
         
     | 
| 
       53 
76 
     | 
    
         
             
                  end
         
     | 
| 
       54 
77 
     | 
    
         | 
| 
       55 
78 
     | 
    
         
             
                  # Core attributes
         
     | 
| 
       56 
79 
     | 
    
         | 
| 
      
 80 
     | 
    
         
            +
                  # Returns the permitted attributes for the create action
         
     | 
| 
      
 81 
     | 
    
         
            +
                  # @return [Array<Symbol>]
         
     | 
| 
       57 
82 
     | 
    
         
             
                  def permitted_attributes_for_create
         
     | 
| 
       58 
     | 
    
         
            -
                     
     | 
| 
      
 83 
     | 
    
         
            +
                    autodetect_permitted_fields(:permitted_attributes_for_create) - [
         
     | 
| 
       59 
84 
     | 
    
         
             
                      context.resource_context.resource_class.primary_key.to_sym, # primary_key
         
     | 
| 
       60 
85 
     | 
    
         
             
                      :created_at, :updated_at # timestamps
         
     | 
| 
       61 
86 
     | 
    
         
             
                    ]
         
     | 
| 
       62 
87 
     | 
    
         
             
                  end
         
     | 
| 
       63 
88 
     | 
    
         | 
| 
      
 89 
     | 
    
         
            +
                  # Returns the permitted attributes for the read action
         
     | 
| 
      
 90 
     | 
    
         
            +
                  # @return [Array<Symbol>]
         
     | 
| 
       64 
91 
     | 
    
         
             
                  def permitted_attributes_for_read
         
     | 
| 
       65 
     | 
    
         
            -
                     
     | 
| 
      
 92 
     | 
    
         
            +
                    autodetect_permitted_fields(:permitted_attributes_for_read)
         
     | 
| 
       66 
93 
     | 
    
         
             
                  end
         
     | 
| 
       67 
94 
     | 
    
         | 
| 
      
 95 
     | 
    
         
            +
                  # Returns the permitted attributes for the update action
         
     | 
| 
      
 96 
     | 
    
         
            +
                  # @return [Array<Symbol>]
         
     | 
| 
       68 
97 
     | 
    
         
             
                  def permitted_attributes_for_update
         
     | 
| 
       69 
98 
     | 
    
         
             
                    permitted_attributes_for_create
         
     | 
| 
       70 
99 
     | 
    
         
             
                  end
         
     | 
| 
       71 
100 
     | 
    
         | 
| 
       72 
101 
     | 
    
         
             
                  # Inferred attributes
         
     | 
| 
       73 
102 
     | 
    
         | 
| 
      
 103 
     | 
    
         
            +
                  # Returns the permitted attributes for the index action
         
     | 
| 
      
 104 
     | 
    
         
            +
                  # @return [Array<Symbol>]
         
     | 
| 
       74 
105 
     | 
    
         
             
                  def permitted_attributes_for_index
         
     | 
| 
       75 
106 
     | 
    
         
             
                    permitted_attributes_for_read
         
     | 
| 
       76 
107 
     | 
    
         
             
                  end
         
     | 
| 
       77 
108 
     | 
    
         | 
| 
      
 109 
     | 
    
         
            +
                  # Returns the permitted attributes for the show action
         
     | 
| 
      
 110 
     | 
    
         
            +
                  # @return [Array<Symbol>]
         
     | 
| 
       78 
111 
     | 
    
         
             
                  def permitted_attributes_for_show
         
     | 
| 
       79 
112 
     | 
    
         
             
                    permitted_attributes_for_read
         
     | 
| 
       80 
113 
     | 
    
         
             
                  end
         
     | 
| 
       81 
114 
     | 
    
         | 
| 
      
 115 
     | 
    
         
            +
                  # Returns the permitted attributes for the new action
         
     | 
| 
      
 116 
     | 
    
         
            +
                  # @return [Array<Symbol>]
         
     | 
| 
       82 
117 
     | 
    
         
             
                  def permitted_attributes_for_new
         
     | 
| 
       83 
118 
     | 
    
         
             
                    permitted_attributes_for_create
         
     | 
| 
       84 
119 
     | 
    
         
             
                  end
         
     | 
| 
       85 
120 
     | 
    
         | 
| 
      
 121 
     | 
    
         
            +
                  # Returns the permitted attributes for the edit action
         
     | 
| 
      
 122 
     | 
    
         
            +
                  # @return [Array<Symbol>]
         
     | 
| 
       86 
123 
     | 
    
         
             
                  def permitted_attributes_for_edit
         
     | 
| 
       87 
124 
     | 
    
         
             
                    permitted_attributes_for_update
         
     | 
| 
       88 
125 
     | 
    
         
             
                  end
         
     | 
| 
       89 
126 
     | 
    
         | 
| 
      
 127 
     | 
    
         
            +
                  # # Returns the permitted associations
         
     | 
| 
      
 128 
     | 
    
         
            +
                  # # @return [Array<Symbol>]
         
     | 
| 
       90 
129 
     | 
    
         
             
                  # def permitted_associations
         
     | 
| 
       91 
130 
     | 
    
         
             
                  #   []
         
     | 
| 
       92 
131 
     | 
    
         
             
                  # end
         
     | 
| 
       93 
132 
     | 
    
         | 
| 
       94 
133 
     | 
    
         
             
                  private
         
     | 
| 
       95 
134 
     | 
    
         | 
| 
       96 
     | 
    
         
            -
                   
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
      
 135 
     | 
    
         
            +
                  # Autodetects the permitted fields for a given method
         
     | 
| 
      
 136 
     | 
    
         
            +
                  # @param [Symbol] method_name The name of the method
         
     | 
| 
      
 137 
     | 
    
         
            +
                  # @return [Array<Symbol>]
         
     | 
| 
      
 138 
     | 
    
         
            +
                  def autodetect_permitted_fields(method_name)
         
     | 
| 
      
 139 
     | 
    
         
            +
                    warn_about_autodetect_usage(method_name)
         
     | 
| 
       98 
140 
     | 
    
         | 
| 
       99 
141 
     | 
    
         
             
                    context.resource_context.resource_class.resource_field_names
         
     | 
| 
       100 
142 
     | 
    
         
             
                  end
         
     | 
| 
       101 
143 
     | 
    
         | 
| 
       102 
     | 
    
         
            -
                   
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
      
 144 
     | 
    
         
            +
                  # Warns about the usage of auto-detection of fields
         
     | 
| 
      
 145 
     | 
    
         
            +
                  # @param [Symbol] method The method name
         
     | 
| 
      
 146 
     | 
    
         
            +
                  # @raise [RuntimeError] if not in development environment
         
     | 
| 
      
 147 
     | 
    
         
            +
                  def warn_about_autodetect_usage(method)
         
     | 
| 
      
 148 
     | 
    
         
            +
                    unless Rails.env.development?
         
     | 
| 
      
 149 
     | 
    
         
            +
                      raise "Resource field auto-detection: #{self.class}##{method} outside development"
         
     | 
| 
      
 150 
     | 
    
         
            +
                    end
         
     | 
| 
       104 
151 
     | 
    
         | 
| 
       105 
     | 
    
         
            -
                     
     | 
| 
      
 152 
     | 
    
         
            +
                    Plutonium.logger.warn %(
         
     | 
| 
       106 
153 
     | 
    
         
             
                      🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
         
     | 
| 
       107 
154 
     | 
    
         | 
| 
       108 
155 
     | 
    
         
             
                      Resource field auto-detection: #{self.class}##{method}
         
     | 
| 
         @@ -1,9 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Plutonium
         
     | 
| 
       2 
2 
     | 
    
         
             
              module Resource
         
     | 
| 
      
 3 
     | 
    
         
            +
                # Presenter class to define actions and fields for a resource
         
     | 
| 
      
 4 
     | 
    
         
            +
                # @abstract
         
     | 
| 
       3 
5 
     | 
    
         
             
                class Presenter
         
     | 
| 
       4 
6 
     | 
    
         
             
                  include Plutonium::Core::Definers::FieldDefiner
         
     | 
| 
       5 
7 
     | 
    
         
             
                  include Plutonium::Core::Definers::ActionDefiner
         
     | 
| 
       6 
8 
     | 
    
         | 
| 
      
 9 
     | 
    
         
            +
                  # Initializes the presenter with context and resource record
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # @param [Object] context The context in which the presenter is used
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # @param [ActiveRecord::Base] resource_record The resource record being presented
         
     | 
| 
       7 
12 
     | 
    
         
             
                  def initialize(context, resource_record)
         
     | 
| 
       8 
13 
     | 
    
         
             
                    @context = context
         
     | 
| 
       9 
14 
     | 
    
         
             
                    @resource_record = resource_record
         
     | 
| 
         @@ -17,14 +22,17 @@ module Plutonium 
     | 
|
| 
       17 
22 
     | 
    
         | 
| 
       18 
23 
     | 
    
         
             
                  attr_reader :context, :resource_record
         
     | 
| 
       19 
24 
     | 
    
         | 
| 
      
 25 
     | 
    
         
            +
                  # Define fields for the resource
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # @note Override this in child presenters for custom field definitions
         
     | 
| 
       20 
27 
     | 
    
         
             
                  def define_fields
         
     | 
| 
       21 
     | 
    
         
            -
                    # override this in child presenters for custom field definitions
         
     | 
| 
       22 
28 
     | 
    
         
             
                  end
         
     | 
| 
       23 
29 
     | 
    
         | 
| 
      
 30 
     | 
    
         
            +
                  # Define actions for the resource
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # @note Override this in child presenters for custom action definitions
         
     | 
| 
       24 
32 
     | 
    
         
             
                  def define_actions
         
     | 
| 
       25 
     | 
    
         
            -
                    # override this in child presenters for custom action definitions
         
     | 
| 
       26 
33 
     | 
    
         
             
                  end
         
     | 
| 
       27 
34 
     | 
    
         | 
| 
      
 35 
     | 
    
         
            +
                  # Define standard actions for the resource
         
     | 
| 
       28 
36 
     | 
    
         
             
                  def define_standard_actions
         
     | 
| 
       29 
37 
     | 
    
         
             
                    define_action Plutonium::Core::Actions::NewAction.new(:new)
         
     | 
| 
       30 
38 
     | 
    
         
             
                    define_action Plutonium::Core::Actions::ShowAction.new(:show)
         
     | 
| 
         @@ -32,12 +40,23 @@ module Plutonium 
     | 
|
| 
       32 
40 
     | 
    
         
             
                    define_action Plutonium::Core::Actions::DestroyAction.new(:destroy)
         
     | 
| 
       33 
41 
     | 
    
         
             
                  end
         
     | 
| 
       34 
42 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                  #  
     | 
| 
      
 43 
     | 
    
         
            +
                  # Define an interactive action
         
     | 
| 
      
 44 
     | 
    
         
            +
                  # @param [Symbol] name The name of the action
         
     | 
| 
      
 45 
     | 
    
         
            +
                  # @param [Object] interaction The interaction object
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # @param [Hash] options Additional options for the action
         
     | 
| 
      
 47 
     | 
    
         
            +
                  # @note This should be moved to its own definer
         
     | 
| 
       36 
48 
     | 
    
         
             
                  def define_interactive_action(name, interaction:, **)
         
     | 
| 
       37 
49 
     | 
    
         
             
                    define_action Plutonium::Core::Actions::InteractiveAction.new(name, interaction:, **)
         
     | 
| 
       38 
50 
     | 
    
         
             
                  end
         
     | 
| 
       39 
51 
     | 
    
         | 
| 
       40 
     | 
    
         
            -
                  #  
     | 
| 
      
 52 
     | 
    
         
            +
                  # Define a nested input for the resource
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # @param [Symbol] name The name of the input
         
     | 
| 
      
 54 
     | 
    
         
            +
                  # @param [Array] inputs The inputs for the nested field
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # @param [Class, nil] model_class The model class for the nested field
         
     | 
| 
      
 56 
     | 
    
         
            +
                  # @param [Hash] options Additional options for the nested field
         
     | 
| 
      
 57 
     | 
    
         
            +
                  # @yield [input] Gives the input object to the block
         
     | 
| 
      
 58 
     | 
    
         
            +
                  # @note This should be moved to its own definer
         
     | 
| 
      
 59 
     | 
    
         
            +
                  # @raise [ArgumentError] if model_class is not provided for polymorphic associations
         
     | 
| 
       41 
60 
     | 
    
         
             
                  def define_nested_input(name, inputs:, model_class: nil, **options)
         
     | 
| 
       42 
61 
     | 
    
         
             
                    nested_attribute_options = resource_class.all_nested_attributes_options[name]
         
     | 
| 
       43 
62 
     | 
    
         | 
| 
         @@ -50,13 +69,7 @@ module Plutonium 
     | 
|
| 
       50 
69 
     | 
    
         
             
                    macro = nested_attribute_options&.[](:macro)
         
     | 
| 
       51 
70 
     | 
    
         
             
                    allow_destroy = nested_attribute_options&.[](:allow_destroy).presence
         
     | 
| 
       52 
71 
     | 
    
         
             
                    update_only = nested_attribute_options&.[](:update_only).presence
         
     | 
| 
       53 
     | 
    
         
            -
                    limit =  
     | 
| 
       54 
     | 
    
         
            -
                      1
         
     | 
| 
       55 
     | 
    
         
            -
                    elsif options.key?(:limit)
         
     | 
| 
       56 
     | 
    
         
            -
                      options[:limit]
         
     | 
| 
       57 
     | 
    
         
            -
                    else
         
     | 
| 
       58 
     | 
    
         
            -
                      nested_attribute_options&.[](:limit)
         
     | 
| 
       59 
     | 
    
         
            -
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
                    limit = determine_nested_input_limit(macro, options[:limit], nested_attribute_options&.[](:limit))
         
     | 
| 
       60 
73 
     | 
    
         | 
| 
       61 
74 
     | 
    
         
             
                    input = Plutonium::Core::Fields::Inputs::NestedInput.new(
         
     | 
| 
       62 
75 
     | 
    
         
             
                      name,
         
     | 
| 
         @@ -72,7 +85,26 @@ module Plutonium 
     | 
|
| 
       72 
85 
     | 
    
         
             
                    define_input name, input:
         
     | 
| 
       73 
86 
     | 
    
         
             
                  end
         
     | 
| 
       74 
87 
     | 
    
         | 
| 
       75 
     | 
    
         
            -
                   
     | 
| 
      
 88 
     | 
    
         
            +
                  # Determines the limit for a nested input
         
     | 
| 
      
 89 
     | 
    
         
            +
                  # @param [Symbol, nil] macro The macro of the association
         
     | 
| 
      
 90 
     | 
    
         
            +
                  # @param [Integer, nil] option_limit The limit provided in options
         
     | 
| 
      
 91 
     | 
    
         
            +
                  # @param [Integer, nil] nested_attribute_limit The limit from nested attributes
         
     | 
| 
      
 92 
     | 
    
         
            +
                  # @return [Integer, nil] The determined limit
         
     | 
| 
      
 93 
     | 
    
         
            +
                  def determine_nested_input_limit(macro, option_limit, nested_attribute_limit)
         
     | 
| 
      
 94 
     | 
    
         
            +
                    if macro == :has_one
         
     | 
| 
      
 95 
     | 
    
         
            +
                      1
         
     | 
| 
      
 96 
     | 
    
         
            +
                    elsif option_limit
         
     | 
| 
      
 97 
     | 
    
         
            +
                      option_limit
         
     | 
| 
      
 98 
     | 
    
         
            +
                    else
         
     | 
| 
      
 99 
     | 
    
         
            +
                      nested_attribute_limit
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                  # Returns the resource class
         
     | 
| 
      
 104 
     | 
    
         
            +
                  # @return [Class] The resource class
         
     | 
| 
      
 105 
     | 
    
         
            +
                  def resource_class
         
     | 
| 
      
 106 
     | 
    
         
            +
                    context.resource_class
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
       76 
108 
     | 
    
         
             
                end
         
     | 
| 
       77 
109 
     | 
    
         
             
              end
         
     | 
| 
       78 
110 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,41 +1,61 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # TODO:  
     | 
| 
      
 1 
     | 
    
         
            +
            # TODO: make standard query type names e.g. search and scope configurable
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            module Plutonium
         
     | 
| 
       4 
     | 
    
         
            -
              # TODO: make standard query type names e.g. search and scope configurable
         
     | 
| 
       5 
4 
     | 
    
         
             
              module Resource
         
     | 
| 
      
 5 
     | 
    
         
            +
                # The QueryObject class is responsible for handling various query types and applying them to the given scope.
         
     | 
| 
       6 
6 
     | 
    
         
             
                class QueryObject
         
     | 
| 
       7 
7 
     | 
    
         
             
                  class << self
         
     | 
| 
       8 
8 
     | 
    
         
             
                  end
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
      
 10 
     | 
    
         
            +
                  # The Query class serves as a base for different types of queries.
         
     | 
| 
       10 
11 
     | 
    
         
             
                  class Query
         
     | 
| 
       11 
12 
     | 
    
         
             
                    include Plutonium::Core::Definers::InputDefiner
         
     | 
| 
       12 
13 
     | 
    
         | 
| 
      
 14 
     | 
    
         
            +
                    # Applies the query to the given scope with the provided parameters.
         
     | 
| 
      
 15 
     | 
    
         
            +
                    #
         
     | 
| 
      
 16 
     | 
    
         
            +
                    # @param scope [Object] the scope to apply the query on
         
     | 
| 
      
 17 
     | 
    
         
            +
                    # @param params [Hash] the parameters for the query
         
     | 
| 
      
 18 
     | 
    
         
            +
                    # @return [Object] the modified scope
         
     | 
| 
       13 
19 
     | 
    
         
             
                    def apply(scope, params)
         
     | 
| 
       14 
     | 
    
         
            -
                      params = extract_query_params 
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                      if input_definitions.size == params.size
         
     | 
| 
       17 
     | 
    
         
            -
                        apply_internal scope, params
         
     | 
| 
       18 
     | 
    
         
            -
                      else
         
     | 
| 
       19 
     | 
    
         
            -
                        scope
         
     | 
| 
       20 
     | 
    
         
            -
                      end
         
     | 
| 
      
 20 
     | 
    
         
            +
                      params = extract_query_params(params)
         
     | 
| 
      
 21 
     | 
    
         
            +
                      (input_definitions.size == params.size) ? apply_internal(scope, params) : scope
         
     | 
| 
       21 
22 
     | 
    
         
             
                    end
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
24 
     | 
    
         
             
                    private
         
     | 
| 
       24 
25 
     | 
    
         | 
| 
      
 26 
     | 
    
         
            +
                    # Raises an error, should be implemented by subclasses.
         
     | 
| 
      
 27 
     | 
    
         
            +
                    #
         
     | 
| 
      
 28 
     | 
    
         
            +
                    # @param scope [Object] the scope to apply the query on
         
     | 
| 
      
 29 
     | 
    
         
            +
                    # @param params [Hash] the parameters for the query
         
     | 
| 
      
 30 
     | 
    
         
            +
                    # @raise [NotImplementedError] if not implemented by subclass
         
     | 
| 
       25 
31 
     | 
    
         
             
                    def apply_internal(scope, params)
         
     | 
| 
       26 
32 
     | 
    
         
             
                      raise NotImplementedError, "#{self.class}#apply_internal"
         
     | 
| 
       27 
33 
     | 
    
         
             
                    end
         
     | 
| 
       28 
34 
     | 
    
         | 
| 
      
 35 
     | 
    
         
            +
                    # Extracts and processes query parameters.
         
     | 
| 
      
 36 
     | 
    
         
            +
                    #
         
     | 
| 
      
 37 
     | 
    
         
            +
                    # @param params [Hash] the parameters to process
         
     | 
| 
      
 38 
     | 
    
         
            +
                    # @return [Hash] the processed parameters
         
     | 
| 
       29 
39 
     | 
    
         
             
                    def extract_query_params(params)
         
     | 
| 
       30 
     | 
    
         
            -
                      input_definitions.collect_all(params).symbolize_keys
         
     | 
| 
      
 40 
     | 
    
         
            +
                      input_definitions.collect_all(params).compact.symbolize_keys
         
     | 
| 
       31 
41 
     | 
    
         
             
                    end
         
     | 
| 
       32 
42 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                     
     | 
| 
      
 43 
     | 
    
         
            +
                    # Returns the resource class, to be implemented by subclasses.
         
     | 
| 
      
 44 
     | 
    
         
            +
                    #
         
     | 
| 
      
 45 
     | 
    
         
            +
                    # @return [Class, nil] the resource class
         
     | 
| 
      
 46 
     | 
    
         
            +
                    def resource_class
         
     | 
| 
      
 47 
     | 
    
         
            +
                      nil
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
       34 
49 
     | 
    
         
             
                  end
         
     | 
| 
       35 
50 
     | 
    
         | 
| 
      
 51 
     | 
    
         
            +
                  # The ScopeQuery class represents a query based on a scope.
         
     | 
| 
       36 
52 
     | 
    
         
             
                  class ScopeQuery < Query
         
     | 
| 
       37 
53 
     | 
    
         
             
                    attr_reader :name
         
     | 
| 
       38 
54 
     | 
    
         | 
| 
      
 55 
     | 
    
         
            +
                    # Initializes a ScopeQuery.
         
     | 
| 
      
 56 
     | 
    
         
            +
                    #
         
     | 
| 
      
 57 
     | 
    
         
            +
                    # @param name [Symbol] the name of the scope
         
     | 
| 
      
 58 
     | 
    
         
            +
                    # @yield [self] optional block to configure the query
         
     | 
| 
       39 
59 
     | 
    
         
             
                    def initialize(name)
         
     | 
| 
       40 
60 
     | 
    
         
             
                      @name = name
         
     | 
| 
       41 
61 
     | 
    
         
             
                      yield self if block_given?
         
     | 
| 
         @@ -43,30 +63,47 @@ module Plutonium 
     | 
|
| 
       43 
63 
     | 
    
         | 
| 
       44 
64 
     | 
    
         
             
                    private
         
     | 
| 
       45 
65 
     | 
    
         | 
| 
      
 66 
     | 
    
         
            +
                    # Applies the scope query to the given scope with the provided parameters.
         
     | 
| 
      
 67 
     | 
    
         
            +
                    #
         
     | 
| 
      
 68 
     | 
    
         
            +
                    # @param scope [Object] the scope to apply the query on
         
     | 
| 
      
 69 
     | 
    
         
            +
                    # @param params [Hash] the parameters for the query
         
     | 
| 
      
 70 
     | 
    
         
            +
                    # @return [Object] the modified scope
         
     | 
| 
       46 
71 
     | 
    
         
             
                    def apply_internal(scope, params)
         
     | 
| 
       47 
     | 
    
         
            -
                      scope.send 
     | 
| 
      
 72 
     | 
    
         
            +
                      scope.send(name, **params)
         
     | 
| 
       48 
73 
     | 
    
         
             
                    end
         
     | 
| 
       49 
74 
     | 
    
         
             
                  end
         
     | 
| 
       50 
75 
     | 
    
         | 
| 
      
 76 
     | 
    
         
            +
                  # The BlockQuery class represents a query based on a block.
         
     | 
| 
       51 
77 
     | 
    
         
             
                  class BlockQuery < Query
         
     | 
| 
       52 
78 
     | 
    
         
             
                    attr_reader :body
         
     | 
| 
       53 
79 
     | 
    
         | 
| 
      
 80 
     | 
    
         
            +
                    # Initializes a BlockQuery.
         
     | 
| 
      
 81 
     | 
    
         
            +
                    #
         
     | 
| 
      
 82 
     | 
    
         
            +
                    # @param body [Proc] the block to apply
         
     | 
| 
      
 83 
     | 
    
         
            +
                    # @yield [self] optional block to configure the query
         
     | 
| 
       54 
84 
     | 
    
         
             
                    def initialize(body)
         
     | 
| 
       55 
85 
     | 
    
         
             
                      @body = body
         
     | 
| 
       56 
86 
     | 
    
         
             
                      yield self if block_given?
         
     | 
| 
       57 
87 
     | 
    
         
             
                    end
         
     | 
| 
       58 
88 
     | 
    
         | 
| 
      
 89 
     | 
    
         
            +
                    private
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                    # Applies the block query to the given scope with the provided parameters.
         
     | 
| 
      
 92 
     | 
    
         
            +
                    #
         
     | 
| 
      
 93 
     | 
    
         
            +
                    # @param scope [Object] the scope to apply the query on
         
     | 
| 
      
 94 
     | 
    
         
            +
                    # @param params [Hash] the parameters for the query
         
     | 
| 
      
 95 
     | 
    
         
            +
                    # @return [Object] the modified scope
         
     | 
| 
       59 
96 
     | 
    
         
             
                    def apply_internal(scope, params)
         
     | 
| 
       60 
     | 
    
         
            -
                       
     | 
| 
       61 
     | 
    
         
            -
                        body.call scope
         
     | 
| 
       62 
     | 
    
         
            -
                      else
         
     | 
| 
       63 
     | 
    
         
            -
                        body.call scope, **params
         
     | 
| 
       64 
     | 
    
         
            -
                      end
         
     | 
| 
      
 97 
     | 
    
         
            +
                      (body.arity == 1) ? body.call(scope) : body.call(scope, **params)
         
     | 
| 
       65 
98 
     | 
    
         
             
                    end
         
     | 
| 
       66 
99 
     | 
    
         
             
                  end
         
     | 
| 
       67 
100 
     | 
    
         | 
| 
       68 
     | 
    
         
            -
                  attr_reader :search_filter, :search_query
         
     | 
| 
      
 101 
     | 
    
         
            +
                  attr_reader :search_filter, :search_query, :context, :selected_sort_fields, :selected_sort_directions, :selected_scope_filter
         
     | 
| 
       69 
102 
     | 
    
         | 
| 
      
 103 
     | 
    
         
            +
                  # Initializes a QueryObject.
         
     | 
| 
      
 104 
     | 
    
         
            +
                  #
         
     | 
| 
      
 105 
     | 
    
         
            +
                  # @param context [Object] the context in which the queries are defined
         
     | 
| 
      
 106 
     | 
    
         
            +
                  # @param params [Hash] the initial parameters for the query object
         
     | 
| 
       70 
107 
     | 
    
         
             
                  def initialize(context, params)
         
     | 
| 
       71 
108 
     | 
    
         
             
                    @context = context
         
     | 
| 
       72 
109 
     | 
    
         | 
| 
         @@ -77,58 +114,61 @@ module Plutonium 
     | 
|
| 
       77 
114 
     | 
    
         | 
| 
       78 
115 
     | 
    
         
             
                    extract_filter_params(params)
         
     | 
| 
       79 
116 
     | 
    
         
             
                    extract_sort_params(params)
         
     | 
| 
       80 
     | 
    
         
            -
                    # @params = params.except(:scope, :search, :sort_fields, :sort_directions)
         
     | 
| 
       81 
117 
     | 
    
         
             
                  end
         
     | 
| 
       82 
118 
     | 
    
         | 
| 
      
 119 
     | 
    
         
            +
                  # Builds a URL with the current query parameters.
         
     | 
| 
      
 120 
     | 
    
         
            +
                  #
         
     | 
| 
      
 121 
     | 
    
         
            +
                  # @param options [Hash] additional options for building the URL
         
     | 
| 
      
 122 
     | 
    
         
            +
                  # @return [String] the built URL
         
     | 
| 
       83 
123 
     | 
    
         
             
                  def build_url(**options)
         
     | 
| 
       84 
124 
     | 
    
         
             
                    q = {}
         
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
                    q[: 
     | 
| 
       87 
     | 
    
         
            -
                    q[:scope] = options.key?(:scope) ? options[:scope].presence : selected_scope_filter
         
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
      
 125 
     | 
    
         
            +
                    q[:search] = options.fetch(:search, search_query).presence
         
     | 
| 
      
 126 
     | 
    
         
            +
                    q[:scope] = options.fetch(:scope, selected_scope_filter).presence
         
     | 
| 
       89 
127 
     | 
    
         
             
                    q[:sort_directions] = selected_sort_directions.dup
         
     | 
| 
       90 
128 
     | 
    
         
             
                    q[:sort_fields] = selected_sort_fields.dup
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
       91 
130 
     | 
    
         
             
                    if (sort = options[:sort])
         
     | 
| 
       92 
     | 
    
         
            -
                       
     | 
| 
       93 
     | 
    
         
            -
                        q[:sort_fields].delete_if { |e| e == sort.to_s }
         
     | 
| 
       94 
     | 
    
         
            -
                        q[:sort_directions].delete sort
         
     | 
| 
       95 
     | 
    
         
            -
                      else
         
     | 
| 
       96 
     | 
    
         
            -
                        q[:sort_fields] << sort.to_s unless q[:sort_fields].include?(sort.to_s)
         
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
                        sort_direction = selected_sort_directions[sort]
         
     | 
| 
       99 
     | 
    
         
            -
                        if sort_direction.nil?
         
     | 
| 
       100 
     | 
    
         
            -
                          q[:sort_directions][sort] = "ASC"
         
     | 
| 
       101 
     | 
    
         
            -
                        elsif sort_direction == "ASC"
         
     | 
| 
       102 
     | 
    
         
            -
                          q[:sort_directions][sort] = "DESC"
         
     | 
| 
       103 
     | 
    
         
            -
                        else
         
     | 
| 
       104 
     | 
    
         
            -
                          q[:sort_fields].delete_if { |e| e == sort.to_s }
         
     | 
| 
       105 
     | 
    
         
            -
                          q[:sort_directions].delete sort
         
     | 
| 
       106 
     | 
    
         
            -
                        end
         
     | 
| 
       107 
     | 
    
         
            -
                      end
         
     | 
| 
      
 131 
     | 
    
         
            +
                      handle_sort_options(q, sort, options[:reset])
         
     | 
| 
       108 
132 
     | 
    
         
             
                    end
         
     | 
| 
       109 
133 
     | 
    
         | 
| 
       110 
134 
     | 
    
         
             
                    "?#{{q: q}.to_param}"
         
     | 
| 
       111 
135 
     | 
    
         
             
                  end
         
     | 
| 
       112 
136 
     | 
    
         | 
| 
      
 137 
     | 
    
         
            +
                  # Applies the queries to the given scope.
         
     | 
| 
      
 138 
     | 
    
         
            +
                  #
         
     | 
| 
      
 139 
     | 
    
         
            +
                  # @param scope [Object] the scope to apply the queries on
         
     | 
| 
      
 140 
     | 
    
         
            +
                  # @return [Object] the modified scope
         
     | 
| 
       113 
141 
     | 
    
         
             
                  def apply(scope)
         
     | 
| 
       114 
142 
     | 
    
         
             
                    scope = search_filter.apply(scope, {search: search_query}) if search_filter.present?
         
     | 
| 
       115 
143 
     | 
    
         
             
                    scope = scope_definitions[selected_scope_filter].apply(scope, {}) if selected_scope_filter.present?
         
     | 
| 
       116 
     | 
    
         
            -
                     
     | 
| 
       117 
     | 
    
         
            -
                      sorter = sort_definitions[name]
         
     | 
| 
       118 
     | 
    
         
            -
                      next unless sorter.present?
         
     | 
| 
       119 
     | 
    
         
            -
             
     | 
| 
       120 
     | 
    
         
            -
                      params = {direction: selected_sort_directions[name] || "ASC"}
         
     | 
| 
       121 
     | 
    
         
            -
                      scope = sorter.apply(scope, params)
         
     | 
| 
       122 
     | 
    
         
            -
                    end
         
     | 
| 
       123 
     | 
    
         
            -
                    scope
         
     | 
| 
      
 144 
     | 
    
         
            +
                    apply_sorters(scope)
         
     | 
| 
       124 
145 
     | 
    
         
             
                  end
         
     | 
| 
       125 
146 
     | 
    
         | 
| 
       126 
     | 
    
         
            -
                   
     | 
| 
      
 147 
     | 
    
         
            +
                  # Retrieves the scope definitions.
         
     | 
| 
      
 148 
     | 
    
         
            +
                  #
         
     | 
| 
      
 149 
     | 
    
         
            +
                  # @return [HashWithIndifferentAccess] the scope definitions
         
     | 
| 
      
 150 
     | 
    
         
            +
                  def scope_definitions
         
     | 
| 
      
 151 
     | 
    
         
            +
                    @scope_definitions ||= {}.with_indifferent_access
         
     | 
| 
      
 152 
     | 
    
         
            +
                  end
         
     | 
| 
       127 
153 
     | 
    
         | 
| 
       128 
     | 
    
         
            -
                   
     | 
| 
      
 154 
     | 
    
         
            +
                  # Retrieves the filter definitions.
         
     | 
| 
      
 155 
     | 
    
         
            +
                  #
         
     | 
| 
      
 156 
     | 
    
         
            +
                  # @return [HashWithIndifferentAccess] the filter definitions
         
     | 
| 
      
 157 
     | 
    
         
            +
                  def filter_definitions
         
     | 
| 
      
 158 
     | 
    
         
            +
                    @filter_definitions ||= {}.with_indifferent_access
         
     | 
| 
      
 159 
     | 
    
         
            +
                  end
         
     | 
| 
       129 
160 
     | 
    
         | 
| 
       130 
     | 
    
         
            -
                   
     | 
| 
      
 161 
     | 
    
         
            +
                  # Retrieves the sort definitions.
         
     | 
| 
      
 162 
     | 
    
         
            +
                  #
         
     | 
| 
      
 163 
     | 
    
         
            +
                  # @return [HashWithIndifferentAccess] the sort definitions
         
     | 
| 
      
 164 
     | 
    
         
            +
                  def sort_definitions
         
     | 
| 
      
 165 
     | 
    
         
            +
                    @sort_definitions ||= {}.with_indifferent_access
         
     | 
| 
      
 166 
     | 
    
         
            +
                  end
         
     | 
| 
       131 
167 
     | 
    
         | 
| 
      
 168 
     | 
    
         
            +
                  # Retrieves the sort parameters for a given name.
         
     | 
| 
      
 169 
     | 
    
         
            +
                  #
         
     | 
| 
      
 170 
     | 
    
         
            +
                  # @param name [Symbol] the name of the sort field
         
     | 
| 
      
 171 
     | 
    
         
            +
                  # @return [Hash, nil] the sort parameters or nil if not defined
         
     | 
| 
       132 
172 
     | 
    
         
             
                  def sort_params_for(name)
         
     | 
| 
       133 
173 
     | 
    
         
             
                    return unless sort_definitions[name]
         
     | 
| 
       134 
174 
     | 
    
         | 
| 
         @@ -142,78 +182,151 @@ module Plutonium 
     | 
|
| 
       142 
182 
     | 
    
         | 
| 
       143 
183 
     | 
    
         
             
                  private
         
     | 
| 
       144 
184 
     | 
    
         | 
| 
       145 
     | 
    
         
            -
                   
     | 
| 
       146 
     | 
    
         
            -
             
     | 
| 
      
 185 
     | 
    
         
            +
                  # Placeholder method for defining filters.
         
     | 
| 
       147 
186 
     | 
    
         
             
                  def define_filters
         
     | 
| 
       148 
187 
     | 
    
         
             
                  end
         
     | 
| 
       149 
188 
     | 
    
         | 
| 
      
 189 
     | 
    
         
            +
                  # Placeholder method for defining scopes.
         
     | 
| 
       150 
190 
     | 
    
         
             
                  def define_scopes
         
     | 
| 
       151 
191 
     | 
    
         
             
                  end
         
     | 
| 
       152 
192 
     | 
    
         | 
| 
      
 193 
     | 
    
         
            +
                  # Placeholder method for defining sorters.
         
     | 
| 
       153 
194 
     | 
    
         
             
                  def define_sorters
         
     | 
| 
       154 
195 
     | 
    
         
             
                  end
         
     | 
| 
       155 
196 
     | 
    
         | 
| 
      
 197 
     | 
    
         
            +
                  # Defines standard queries.
         
     | 
| 
       156 
198 
     | 
    
         
             
                  def define_standard_queries
         
     | 
| 
       157 
199 
     | 
    
         
             
                    define_search(:search) if resource_class.respond_to?(:search)
         
     | 
| 
       158 
200 
     | 
    
         
             
                  end
         
     | 
| 
       159 
201 
     | 
    
         | 
| 
       160 
     | 
    
         
            -
                   
     | 
| 
      
 202 
     | 
    
         
            +
                  # Defines a filter.
         
     | 
| 
      
 203 
     | 
    
         
            +
                  #
         
     | 
| 
      
 204 
     | 
    
         
            +
                  # @param name [Symbol] the name of the filter
         
     | 
| 
      
 205 
     | 
    
         
            +
                  # @param body [Proc, nil] the body of the filter
         
     | 
| 
      
 206 
     | 
    
         
            +
                  # @yield [Query] optional block to configure the query
         
     | 
| 
      
 207 
     | 
    
         
            +
                  def define_filter(name, body = nil, &block)
         
     | 
| 
       161 
208 
     | 
    
         
             
                    body ||= name
         
     | 
| 
       162 
     | 
    
         
            -
                    filter_definitions[name] = build_query(body, &)
         
     | 
| 
      
 209 
     | 
    
         
            +
                    filter_definitions[name] = build_query(body, &block)
         
     | 
| 
       163 
210 
     | 
    
         
             
                  end
         
     | 
| 
       164 
211 
     | 
    
         | 
| 
      
 212 
     | 
    
         
            +
                  # Defines a scope.
         
     | 
| 
      
 213 
     | 
    
         
            +
                  #
         
     | 
| 
      
 214 
     | 
    
         
            +
                  # @param name [Symbol] the name of the scope
         
     | 
| 
      
 215 
     | 
    
         
            +
                  # @param body [Proc, nil] the body of the scope
         
     | 
| 
       165 
216 
     | 
    
         
             
                  def define_scope(name, body = nil)
         
     | 
| 
       166 
217 
     | 
    
         
             
                    body ||= name
         
     | 
| 
       167 
218 
     | 
    
         
             
                    scope_definitions[name] = build_query(body)
         
     | 
| 
       168 
219 
     | 
    
         
             
                  end
         
     | 
| 
       169 
220 
     | 
    
         | 
| 
      
 221 
     | 
    
         
            +
                  # Defines a sort.
         
     | 
| 
      
 222 
     | 
    
         
            +
                  #
         
     | 
| 
      
 223 
     | 
    
         
            +
                  # @param name [Symbol] the name of the sort field
         
     | 
| 
      
 224 
     | 
    
         
            +
                  # @param body [Proc, nil] the body of the sort
         
     | 
| 
       170 
225 
     | 
    
         
             
                  def define_sort(name, body = nil)
         
     | 
| 
       171 
226 
     | 
    
         
             
                    if body.nil?
         
     | 
| 
       172 
     | 
    
         
            -
                      sort_field =  
     | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
       174 
     | 
    
         
            -
                      elsif resource_class.belongs_to_association_field_names.include? name
         
     | 
| 
       175 
     | 
    
         
            -
                        resource_class.reflect_on_association(name).foreign_key.to_sym
         
     | 
| 
       176 
     | 
    
         
            -
                      else
         
     | 
| 
       177 
     | 
    
         
            -
                        raise "Unable to determine sort logic for '#{body}'"
         
     | 
| 
       178 
     | 
    
         
            -
                      end
         
     | 
| 
       179 
     | 
    
         
            -
                      body = lambda { |scope, direction:| scope.order(sort_field => direction) }
         
     | 
| 
      
 227 
     | 
    
         
            +
                      sort_field = determine_sort_field(name)
         
     | 
| 
      
 228 
     | 
    
         
            +
                      body = ->(scope, direction:) { scope.order(sort_field => direction) }
         
     | 
| 
       180 
229 
     | 
    
         
             
                    end
         
     | 
| 
       181 
     | 
    
         
            -
             
     | 
| 
       182 
230 
     | 
    
         
             
                    sort_definitions[name] = build_query(body) do |query|
         
     | 
| 
       183 
231 
     | 
    
         
             
                      query.define_input :direction
         
     | 
| 
       184 
232 
     | 
    
         
             
                    end
         
     | 
| 
       185 
233 
     | 
    
         
             
                  end
         
     | 
| 
       186 
234 
     | 
    
         | 
| 
      
 235 
     | 
    
         
            +
                  # Defines a search filter.
         
     | 
| 
      
 236 
     | 
    
         
            +
                  #
         
     | 
| 
      
 237 
     | 
    
         
            +
                  # @param body [Proc] the body of the search filter
         
     | 
| 
       187 
238 
     | 
    
         
             
                  def define_search(body)
         
     | 
| 
       188 
239 
     | 
    
         
             
                    @search_filter = build_query(body) do |query|
         
     | 
| 
       189 
240 
     | 
    
         
             
                      query.define_input :search
         
     | 
| 
       190 
241 
     | 
    
         
             
                    end
         
     | 
| 
       191 
242 
     | 
    
         
             
                  end
         
     | 
| 
       192 
243 
     | 
    
         | 
| 
      
 244 
     | 
    
         
            +
                  # Extracts filter parameters from the given params.
         
     | 
| 
      
 245 
     | 
    
         
            +
                  #
         
     | 
| 
      
 246 
     | 
    
         
            +
                  # @param params [Hash] the parameters to extract from
         
     | 
| 
       193 
247 
     | 
    
         
             
                  def extract_filter_params(params)
         
     | 
| 
       194 
248 
     | 
    
         
             
                    @search_query = params[:search]
         
     | 
| 
       195 
249 
     | 
    
         
             
                    @selected_scope_filter = params[:scope]
         
     | 
| 
       196 
250 
     | 
    
         
             
                  end
         
     | 
| 
       197 
251 
     | 
    
         | 
| 
      
 252 
     | 
    
         
            +
                  # Extracts sort parameters from the given params.
         
     | 
| 
      
 253 
     | 
    
         
            +
                  #
         
     | 
| 
      
 254 
     | 
    
         
            +
                  # @param params [Hash] the parameters to extract from
         
     | 
| 
       198 
255 
     | 
    
         
             
                  def extract_sort_params(params)
         
     | 
| 
       199 
     | 
    
         
            -
                    @selected_sort_fields = Array(params[:sort_fields])
         
     | 
| 
       200 
     | 
    
         
            -
                    @ 
     | 
| 
       201 
     | 
    
         
            -
             
     | 
| 
       202 
     | 
    
         
            -
                    @selected_sort_directions = params[:sort_directions]&.slice(*sort_definitions.keys) || {}
         
     | 
| 
       203 
     | 
    
         
            -
                    @selected_sort_directions = @selected_sort_directions.map { |key, value| [key, {"DESC" => "DESC"}[value.upcase] || "ASC"] }.to_h.with_indifferent_access
         
     | 
| 
      
 256 
     | 
    
         
            +
                    @selected_sort_fields = Array(params[:sort_fields]) & sort_definitions.keys
         
     | 
| 
      
 257 
     | 
    
         
            +
                    @selected_sort_directions = (params[:sort_directions]&.slice(*sort_definitions.keys) || {}).transform_values { |v| (v.upcase == "DESC") ? "DESC" : "ASC" }.with_indifferent_access
         
     | 
| 
       204 
258 
     | 
    
         
             
                  end
         
     | 
| 
       205 
259 
     | 
    
         | 
| 
       206 
     | 
    
         
            -
                   
     | 
| 
      
 260 
     | 
    
         
            +
                  # Builds a query object.
         
     | 
| 
      
 261 
     | 
    
         
            +
                  #
         
     | 
| 
      
 262 
     | 
    
         
            +
                  # @param body [Symbol, Proc] the body of the query
         
     | 
| 
      
 263 
     | 
    
         
            +
                  # @yield [Query] optional block to configure the query
         
     | 
| 
      
 264 
     | 
    
         
            +
                  # @return [Query] the built query object
         
     | 
| 
      
 265 
     | 
    
         
            +
                  def build_query(body, &block)
         
     | 
| 
       207 
266 
     | 
    
         
             
                    case body
         
     | 
| 
       208 
267 
     | 
    
         
             
                    when Symbol
         
     | 
| 
       209 
     | 
    
         
            -
                      raise "Cannot find scope :#{body} on #{resource_class}" unless resource_class.respond_to? 
     | 
| 
       210 
     | 
    
         
            -
                      ScopeQuery.new(body, &)
         
     | 
| 
      
 268 
     | 
    
         
            +
                      raise "Cannot find scope :#{body} on #{resource_class}" unless resource_class.respond_to?(body)
         
     | 
| 
      
 269 
     | 
    
         
            +
                      ScopeQuery.new(body, &block)
         
     | 
| 
      
 270 
     | 
    
         
            +
                    else
         
     | 
| 
      
 271 
     | 
    
         
            +
                      BlockQuery.new(body, &block)
         
     | 
| 
      
 272 
     | 
    
         
            +
                    end
         
     | 
| 
      
 273 
     | 
    
         
            +
                  end
         
     | 
| 
      
 274 
     | 
    
         
            +
             
     | 
| 
      
 275 
     | 
    
         
            +
                  # Retrieves the resource class.
         
     | 
| 
      
 276 
     | 
    
         
            +
                  #
         
     | 
| 
      
 277 
     | 
    
         
            +
                  # @return [Class] the resource class
         
     | 
| 
      
 278 
     | 
    
         
            +
                  def resource_class
         
     | 
| 
      
 279 
     | 
    
         
            +
                    context.resource_class
         
     | 
| 
      
 280 
     | 
    
         
            +
                  end
         
     | 
| 
      
 281 
     | 
    
         
            +
             
     | 
| 
      
 282 
     | 
    
         
            +
                  # Determines the sort field for a given name.
         
     | 
| 
      
 283 
     | 
    
         
            +
                  #
         
     | 
| 
      
 284 
     | 
    
         
            +
                  # @param name [Symbol] the name of the sort field
         
     | 
| 
      
 285 
     | 
    
         
            +
                  # @return [Symbol] the determined sort field
         
     | 
| 
      
 286 
     | 
    
         
            +
                  # @raise [RuntimeError] if unable to determine sort logic for the field
         
     | 
| 
      
 287 
     | 
    
         
            +
                  def determine_sort_field(name)
         
     | 
| 
      
 288 
     | 
    
         
            +
                    if resource_class.primary_key == name.to_s || resource_class.content_column_field_names.include?(name)
         
     | 
| 
      
 289 
     | 
    
         
            +
                      name
         
     | 
| 
      
 290 
     | 
    
         
            +
                    elsif resource_class.belongs_to_association_field_names.include?(name)
         
     | 
| 
      
 291 
     | 
    
         
            +
                      resource_class.reflect_on_association(name).foreign_key.to_sym
         
     | 
| 
      
 292 
     | 
    
         
            +
                    else
         
     | 
| 
      
 293 
     | 
    
         
            +
                      raise "Unable to determine sort logic for '#{name}'"
         
     | 
| 
      
 294 
     | 
    
         
            +
                    end
         
     | 
| 
      
 295 
     | 
    
         
            +
                  end
         
     | 
| 
      
 296 
     | 
    
         
            +
             
     | 
| 
      
 297 
     | 
    
         
            +
                  # Handles sorting options.
         
     | 
| 
      
 298 
     | 
    
         
            +
                  #
         
     | 
| 
      
 299 
     | 
    
         
            +
                  # @param query [Hash] the query parameters
         
     | 
| 
      
 300 
     | 
    
         
            +
                  # @param sort [Symbol] the sort field
         
     | 
| 
      
 301 
     | 
    
         
            +
                  # @param reset [Boolean] whether to reset the sorting
         
     | 
| 
      
 302 
     | 
    
         
            +
                  def handle_sort_options(query, sort, reset)
         
     | 
| 
      
 303 
     | 
    
         
            +
                    if reset
         
     | 
| 
      
 304 
     | 
    
         
            +
                      query[:sort_fields].delete_if { |e| e == sort.to_s }
         
     | 
| 
      
 305 
     | 
    
         
            +
                      query[:sort_directions].delete(sort)
         
     | 
| 
       211 
306 
     | 
    
         
             
                    else
         
     | 
| 
       212 
     | 
    
         
            -
                       
     | 
| 
      
 307 
     | 
    
         
            +
                      query[:sort_fields] << sort.to_s unless query[:sort_fields].include?(sort.to_s)
         
     | 
| 
      
 308 
     | 
    
         
            +
                      sort_direction = selected_sort_directions[sort]
         
     | 
| 
      
 309 
     | 
    
         
            +
                      query[:sort_directions][sort] = if sort_direction.nil?
         
     | 
| 
      
 310 
     | 
    
         
            +
                        "ASC"
         
     | 
| 
      
 311 
     | 
    
         
            +
                      else
         
     | 
| 
      
 312 
     | 
    
         
            +
                        ((sort_direction == "ASC") ? "DESC" : "ASC")
         
     | 
| 
      
 313 
     | 
    
         
            +
                      end
         
     | 
| 
      
 314 
     | 
    
         
            +
                      query[:sort_fields].delete_if { |e| e == sort.to_s } if query[:sort_directions][sort] == "ASC"
         
     | 
| 
       213 
315 
     | 
    
         
             
                    end
         
     | 
| 
       214 
316 
     | 
    
         
             
                  end
         
     | 
| 
       215 
317 
     | 
    
         | 
| 
       216 
     | 
    
         
            -
                   
     | 
| 
      
 318 
     | 
    
         
            +
                  # Applies sorters to the scope.
         
     | 
| 
      
 319 
     | 
    
         
            +
                  #
         
     | 
| 
      
 320 
     | 
    
         
            +
                  # @param scope [Object] the scope to apply sorters on
         
     | 
| 
      
 321 
     | 
    
         
            +
                  # @return [Object] the modified scope
         
     | 
| 
      
 322 
     | 
    
         
            +
                  def apply_sorters(scope)
         
     | 
| 
      
 323 
     | 
    
         
            +
                    selected_sort_fields.each do |name|
         
     | 
| 
      
 324 
     | 
    
         
            +
                      sorter = sort_definitions[name]
         
     | 
| 
      
 325 
     | 
    
         
            +
                      next unless sorter.present?
         
     | 
| 
      
 326 
     | 
    
         
            +
                      scope = sorter.apply(scope, {direction: selected_sort_directions[name] || "ASC"})
         
     | 
| 
      
 327 
     | 
    
         
            +
                    end
         
     | 
| 
      
 328 
     | 
    
         
            +
                    scope
         
     | 
| 
      
 329 
     | 
    
         
            +
                  end
         
     | 
| 
       217 
330 
     | 
    
         
             
                end
         
     | 
| 
       218 
331 
     | 
    
         
             
              end
         
     | 
| 
       219 
332 
     | 
    
         
             
            end
         
     |