plutonium 0.10.1 → 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 +0 -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
|