Create an EasyQuery¶
An EasyQuery lists and filters the records of one model on a page. It defines the available filters and columns; users combine them with grouping and sorting. The same query class backs dashboards (EasyPage) and EasyGraphql types.
Place the class in your plugin's app/easy_queries/ and inherit from EasyQuery. Name it after the model with the suffix Query: MyModelQuery for MyModel. Do not namespace the class, because EasyGraphql::Extensions::EasyQuery does not support namespaced queries; prefix the name to match your controller instead.
Users can also write filters as text; see Easy Query Language.
Structure¶
Set queried_class first; every query method can use it. Then implement initialize_available_filters and initialize_available_columns.
A query is rendered in one of two forms. The default form has static cells. A dynamic query lets users edit column values in the table cell, which changes how columns are defined (see the column options below). Dynamic queries are supported for EasyIssueQuery, ProjectQuery and UserQuery.
initialize_available_filters¶
This method defines the available filters. It runs lazily when available_filters first needs its catalogue, and again when the catalogue cache is invalidated (for example by EasyIssueQuery#project=).
Define a filter with add_available_filter(name, options). The name is the database column name. The :type option sets the available operators and the input field; the default is :string. The type also determines which other options are required.
-
:listand:list_optionalneedvalues:, usually aprocthat returns[label, value]pairs: -
:list_autocompleteand:treeneedsource:andsource_root:.sourceis the autocomplete method, usually served byauto_completes_controller;source_rootdetermines how the individual objects behave. Addsource_options:for extra parameters such asproject_id. When the column name does not match the class name, pass the class withklass:. -
For
Principaldescendants (author_id,user_id,assignee_id) useadd_principal_autocomplete_filter, which sets all the options above: -
For dates use
type: :date_period. The:dateand:date_pasttypes are deprecated and are converted to:date_periodwith a warning.
Every filter loads only values the current user may see, including the values: arrays and procs.
initialize_available_columns¶
This method defines the available columns. Define a column with add_column(name, type, options). The name matches the entity property; for an association use the association name, not the database column. The type describes the cell and should follow the database column type. Use :record for associations and enums.
Common options:
sortable: SQL used to sort by the column, usually"table_name.column". Use an array of strings for several sort criteria.default_order: default sort direction of a sortable column.groupable: SQL used to group by the column, written likesortable.trueis allowed when the column name equals the table column of the queried entity.most_used: boolean, defaultfalse.preload/includes: associations to load or join, as an array of symbols. When you sort through another table, addincludesfor that association too.
add_column :name,
:string,
sortable: "#{::MyModel.table_name}.name"
add_column :status,
:record,
sortable: "#{::MyModel.table_name}.status"
add_column :collection, :record,
sortable: "#{::Collection.table_name}.name",
groupable: "#{::MyModel.table_name}.collection_id",
preload: [:collection]
To edit an association in a dynamic query, the column needs an autocomplete. Add these options:
attribute: the association's column name.ref: the referenced class.source_options: autocomplete settings withsource,source_root(both as in filters) andparams(extra parameters for the source method).
add_column :project, :record, most_used: true,
sortable: "#{Project.table_name}.name",
groupable: "#{Issue.table_name}.project_id",
includes: [:project],
attribute: "project_id", ref: :itself,
source_options: { source: "allowed_target_projects_on_move", source_root: "projects" }
For Principal descendants use add_principal_column:
default_list_columns¶
Return the column names (strings) shown when the user has no saved column setting:
Use the query in a controller¶
Include the query helpers in the controller and call index_for_easy_query from the action. See Controllers for the rest of the controller.
Render the query in the view with <%= render @query %>.
Scope construction¶
Use entities_scope(options = {}) when you need an ActiveRecord::Relation. Use entities(options = {}) only when you need an array of records.
Runtime scope assignment is public; the defaults of a query type are protected hooks that subclasses override:
| Public runtime API | Protected default hook | Purpose |
|---|---|---|
entity_scope= / set_entity_scope |
base_entity_scope |
Starting relation; returns an ActiveRecord::Relation or model class. |
additional_scope= / add_additional_scope |
default_additional_scope |
Relation or proc merged after the generated filter SQL; returns a relation, proc or nil. |
additional_statement / add_additional_statement |
default_additional_statement |
Trusted SQL condition without WHERE; returns a string or nil. |
| None | additional_filter_statement |
Trusted SQL produced by custom filter composition; returns a string or nil. |
An assigned entity_scope replaces base_entity_scope, including the visibility rules it applied. The relation you assign must already enforce authorisation. Assigning nil through additional_scope= suppresses default_additional_scope; reset_additional_scope removes the runtime override. A nil entity_scope= falls back to base_entity_scope.
The SQL statement APIs and hooks accept trusted SQL only. Never interpolate request or user values into additional_statement, add_additional_statement, default_additional_statement or additional_filter_statement; use relation or hash conditions and bound parameters through the scope APIs.
Free search returns 25 records by default; pass limit: for another limit.
Override the defaults below protected:
class MyModelQuery < EasyQuery
protected
def base_entity_scope
super.where(active: true)
end
def default_additional_scope
project ? queried_class.where(project:) : nil
end
end
create_entity_scope and new_entity_scope are deprecated; use entities_scope. The relation you pass to new_entity_scope replaces entity_scope and the generated statement and bypasses authorisation-sensitive entity_scope overrides, so it must already be authorised.
Filters in server-rendered dialogs¶
The easy_queries/filters partial accepts these optional locals for embedded selectors:
| Local | Purpose |
|---|---|
initialization_content_for |
Captures the initialiser in a named content_for slot instead of rendering it inline. Render the body first, then emit the slot once, outside dialog content that a custom element moves. |
list_values |
A hash of filter names to [[label, id], ...] arrays. The widget uses them synchronously; omitted keys keep the standard AJAX loader. An empty array is a loaded empty list. |
query_string_enabled: false |
Hides the query-language controls. It does not replace server-side validation of permitted filter parameters. |
Embed only values the user may see, and check authorisation again when loading results and confirming a selection. Preloaded values enlarge the initial HTML, so use them for bounded catalogues.
To specialise an existing query for a picker, subclass it in your plugin's app/easy_queries/, override initialize_available_filters, call super, then narrow the catalogue there. Rewriting an instance's catalogue elsewhere is lost when the cache is invalidated. Load option values from the subclass's own authorised scope, and return empty values when the candidate context is missing. Such queries are constructed directly and need no registration.
Dashboard output forms¶
A registered query can be used in EasyPage dashboards in several forms. Each query tells which forms it supports (defaults in brackets):
list_support?(true),report_support?(true),tiles_support?(true)chart_support?(false),calendar_support?(false),reactive_support?(false)
The *_support? methods exist as class methods; instances delegate to the class. Override the class method:
display_entity_count? also defaults to true. Override self.display_entity_count? to return false in query types that do not support counts.
Registration¶
Register the query so it can be used in dashboards. Add the registration to an initialiser of your plugin, for example config/initializers/easy_query_registration.rb, inside EasyInitHelper.on_constant_autoload("EasyQuery") so the registry stays lazy-loaded and reload-safe. Do not register through ActiveSupport.on_load or after_initialize.
| config/initializers/easy_query_registration.rb | |
|---|---|
See EasyInitHelper registration helpers for the general rules.