Class: Plutonium::Resource::QueryObject

Inherits:
Object
  • Object
show all
Defined in:
lib/plutonium/resource/query_object.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(resource_class, params, request_path) {|_self| ... } ⇒ QueryObject

Initializes a QueryObject with the given resource_class and parameters.

Parameters:

  • resource_class (Object)

    The resource class.

  • params (Hash)

    The parameters for initialization.

Yields:

  • (_self)

Yield Parameters:



20
21
22
23
24
25
26
27
28
29
# File 'lib/plutonium/resource/query_object.rb', line 20

def initialize(resource_class, params, request_path, &)
  @resource_class = resource_class
  @params = params
  @request_path = request_path

  define_standard_queries
  yield self if block_given?
  extract_filter_params
  extract_sort_params
end

Instance Attribute Details

#default_scope_nameObject

Returns the value of attribute default_scope_name.



14
15
16
# File 'lib/plutonium/resource/query_object.rb', line 14

def default_scope_name
  @default_scope_name
end

#default_sort_configObject

Returns the value of attribute default_sort_config.



14
15
16
# File 'lib/plutonium/resource/query_object.rb', line 14

def default_sort_config
  @default_sort_config
end

#request_pathObject (readonly)

The path every URL this object builds is anchored to — the collection page, which is not necessarily the current request (see Plutonium::Core::Controller#current_page_path). Exposed so the components rendering alongside these URLs — the search form's action, the filter pills' clear links — address the same page instead of each re-deriving it from request.



12
13
14
# File 'lib/plutonium/resource/query_object.rb', line 12

def request_path
  @request_path
end

#search_filterObject (readonly)

Returns the value of attribute search_filter.



4
5
6
# File 'lib/plutonium/resource/query_object.rb', line 4

def search_filter
  @search_filter
end

#search_queryObject (readonly)

Returns the value of attribute search_query.



4
5
6
# File 'lib/plutonium/resource/query_object.rb', line 4

def search_query
  @search_query
end

Instance Method Details

#active_filter_descriptionsObject

Returns an array of hashes describing each currently active filter. Each hash has: name, label, value_label, clear_url



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/plutonium/resource/query_object.rb', line 154

def active_filter_descriptions
  filter_definitions.filter_map do |name, filter|
    name = name.to_sym
    filter_params = params[name]
    next unless filter_params.present?

    value_label = case filter_params
    when Hash, ActionController::Parameters
      entries = filter_params.to_h.reject { |_, v| v.blank? }
      next if entries.empty?
      # Single-input filters defer to the filter's `humanize_value`
      # (e.g. Association resolves ids to labels, Boolean translates
      # "true" -> "Yes"). Multi-input filters keep input-name
      # qualifiers (e.g. "from 2024, to 2025").
      if entries.size == 1
        humanized = filter.humanize_value(entries.values.first)
        next if humanized.blank?
        humanized
      else
        entries.map { |k, v| "#{k.to_s.humanize.downcase} #{v}" }.join(", ")
      end
    when Array
      entries = filter_params.reject(&:blank?)
      next if entries.empty?
      humanized = filter.humanize_value(entries)
      next if humanized.blank?
      humanized
    else
      next if filter_params.to_s.blank?
      humanized = filter.humanize_value(filter_params)
      next if humanized.blank?
      humanized
    end

    {
      name: name,
      label: name.to_s.humanize,
      value_label: value_label,
      clear_url: build_url(name => nil)
    }
  end
end

#all_scope_selected?Boolean

Returns true if user explicitly selected "All" scope (no filtering)

Returns:

  • (Boolean)


143
# File 'lib/plutonium/resource/query_object.rb', line 143

def all_scope_selected? = @all_scope_selected

#apply(scope, params, context: nil) ⇒ Object

Applies the defined filters and sorts to the given scope.

Parameters:

  • scope (Object)

    The initial scope to which filters and sorts are applied.

  • params (Hash)

    The query parameters.

  • context (Object) (defaults to: nil)

    Optional context (e.g., controller) for executing scope blocks.

Returns:

  • (Object)

    The modified scope.



120
121
122
123
124
125
126
127
128
# File 'lib/plutonium/resource/query_object.rb', line 120

def apply(scope, params, context: nil)
  params = deep_compact(params.with_indifferent_access)
  scope = search_filter.apply(scope, search: params[:search]) if search_filter && params[:search]
  # Use selected_scope which includes the default when no explicit selection
  effective_scope = @selected_scope_filter
  scope = scope_definitions[effective_scope].apply(scope, context:) if effective_scope && scope_definitions[effective_scope]
  scope = apply_sorts(scope, params)
  apply_filters(scope, params)
end

#build_url(**options) ⇒ String

Builds a URL with the given options for search and sorting.

Parameters:

  • options (Hash)

    The options for building the URL.

Options Hash (**options):

  • :replace (Boolean)

    When true, clears all existing sorts before applying the new one

Returns:

  • (String)

    The constructed URL with query parameters.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/plutonium/resource/query_object.rb', line 79

def build_url(**options)
  q = {}

  q[:search] = options.key?(:search) ? options[:search].presence : search_query
  q[:scope] = if options.key?(:scope)
    options[:scope].presence
  else
    selected_scope_filter
  end

  if options.delete(:replace)
    q[:sort_directions] = {}
    q[:sort_fields] = []
  else
    q[:sort_directions] = selected_sort_directions.dup
    q[:sort_fields] = selected_sort_fields.dup
  end
  handle_sort_options!(q, options)

  filter_keys = filter_definitions.keys.map(&:to_sym)
  filter_overrides = options.slice(*filter_keys).stringify_keys
  q.merge! params.with_indifferent_access.slice(*filter_definitions.keys)
  q.merge!(filter_overrides)
  compacted = deep_compact({q: q})

  # Preserve explicit "All" selection (scope: nil in options means show all)
  if options.key?(:scope) && options[:scope].nil?
    compacted[:q] ||= {}
    compacted[:q][:scope] = ""
  end

  query_params = compacted.to_param
  "#{@request_path}?#{query_params}"
end

#define_filter(name, body) ⇒ Object

Defines a filter with the given name and body.

Parameters:

  • name (Symbol)

    The name of the filter.

  • body (Proc, nil)

    The body of the filter.



35
36
37
# File 'lib/plutonium/resource/query_object.rb', line 35

def define_filter(name, body, &)
  filter_definitions[name] = build_query(body, &)
end

#define_scope(name, body = nil, condition: nil) ⇒ Object

Defines a scope with the given name and body.

Parameters:

  • name (Symbol)

    The name of the scope.

  • body (Proc, nil) (defaults to: nil)

    The body of the scope.

  • condition (Proc, nil) (defaults to: nil)

    Display-only visibility gate (same semantics as condition: on actions).



44
45
46
47
48
# File 'lib/plutonium/resource/query_object.rb', line 44

def define_scope(name, body = nil, condition: nil, **)
  body ||= name
  scope_definitions[name] = build_query(body)
  scope_conditions[name] = condition if condition
end

#define_search(body) ⇒ Object

Defines a search filter with the given body.

Parameters:

  • body (Proc, Symbol)

    The body of the search filter.



68
69
70
71
72
# File 'lib/plutonium/resource/query_object.rb', line 68

def define_search(body)
  @search_filter = build_query(body) do |query|
    query.input :search
  end
end

#define_sorter(name, body = nil, using: nil) ⇒ Object

Defines a sort with the given name and body.

Parameters:

  • name (Symbol)

    The name of the sort.

  • body (Proc, nil) (defaults to: nil)

    The body of the sort.



54
55
56
57
58
59
60
61
62
63
# File 'lib/plutonium/resource/query_object.rb', line 54

def define_sorter(name, body = nil, using: nil)
  if body.nil?
    sort_field = using || determine_sort_field(name)
    body = ->(scope, direction:) { scope.order(sort_field => direction) }
  end

  sort_definitions[name] = build_query(body) do |query|
    query.input :direction
  end
end

#filter_definitionsObject



148
# File 'lib/plutonium/resource/query_object.rb', line 148

def filter_definitions = @filter_definitions ||= {}.with_indifferent_access

#scope_conditionsObject



132
# File 'lib/plutonium/resource/query_object.rb', line 132

def scope_conditions = @scope_conditions ||= {}.with_indifferent_access

#scope_definitionsObject



130
# File 'lib/plutonium/resource/query_object.rb', line 130

def scope_definitions = @scope_definitions ||= {}.with_indifferent_access

#scope_visible?(name, view_context) ⇒ Boolean

Display-only visibility gate for a scope, mirroring condition: on actions. Returns true when no condition is set.

Returns:

  • (Boolean)


136
137
138
139
140
# File 'lib/plutonium/resource/query_object.rb', line 136

def scope_visible?(name, view_context)
  condition = scope_conditions[name]
  return true if condition.nil?
  Plutonium::Action::ConditionContext.new(view_context, nil).instance_exec(&condition)
end

#selected_scopeObject

Returns the currently selected scope (may be default if none explicitly selected)



146
# File 'lib/plutonium/resource/query_object.rb', line 146

def selected_scope = @selected_scope_filter

#sort_definitionsObject



150
# File 'lib/plutonium/resource/query_object.rb', line 150

def sort_definitions = @sort_definitions ||= {}.with_indifferent_access

#sort_params_for(name) ⇒ Hash?

Provides sorting parameters for the given field name.

Parameters:

  • name (Symbol, String)

    The name of the field to sort.

Returns:

  • (Hash, nil)

    The sorting parameters including URL, multi_url, direction, position and multi flag.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/plutonium/resource/query_object.rb', line 201

def sort_params_for(name)
  return unless sort_definitions[name]

  multi = selected_sort_fields.size > 1 && selected_sort_fields.include?(name.to_s)

  {
    url: build_url(sort: name, replace: true),
    multi_url: build_url(sort: name),
    reset_url: build_url(sort: name, reset: true),
    position: selected_sort_fields.index(name.to_s),
    direction: selected_sort_directions[name],
    multi: multi
  }
end

#sorted_ascending_only_by?(name) ⇒ Boolean

Whether the collection is ordered by name ASCENDING and by nothing else — the invariant a drag-reorder depends on. Dropping a row "between the two rows either side of it" only means anything when the visual order IS the stored order: under a title sort the neighbours say nothing about position, and under a DESCENDING position sort they say the opposite of what the write assumes.

An explicit selection wins; with none, the resource's default_sort is what the user is looking at (field/direction form only — a default_sort block is opaque, so it cannot be claimed to satisfy this).

Parameters:

  • name (Symbol, String)

    the position attribute

Returns:

  • (Boolean)


229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/plutonium/resource/query_object.rb', line 229

def sorted_ascending_only_by?(name)
  fields = selected_sort_fields.map(&:to_s)

  if fields.any?
    return false unless fields == [name.to_s]
    direction = selected_sort_directions[name.to_sym] || selected_sort_directions[name.to_s] || "ASC"
    return direction.to_s.casecmp("asc").zero?
  end

  return false unless default_sort_config.is_a?(Array)
  field, direction = default_sort_config
  field.to_s == name.to_s && (direction || :asc).to_s.casecmp("asc").zero?
end