How to create new EasyQuery
This section provide basic information what needs to be implemented for new working easy query.
Easy Query
Easy Query is tool which allows user to display and filter records of specific model within web page. Each Easy Query has defined available filters and columns. User may use them, along with grouping and ordering, combine it all together to achieve desired results and display records he wants to see.
Easy Query is also used by EasyGraphql extension to easily define GraphQL queries for various types.
Easy queries source codes are located in directory app/easy_queries. Easy query must be inherited from EasyQuery class.
New easy query name is name of model with suffix EasyQuery. For example MyModelQuery for model MyModel.
Every easy query need to be registered to be able use it within dashboards.
Although it is possible to namespace easy queries, for now EasyGraphql::Extensions::EasyQuery extension does not support it.
So it is recommended to keep corresponding with controllers and use prefixes.
Easy Query structure
In the begging is good practice to assign queried_class to easy query. It can be used within all query methods.
EasyQuery has two important methods to implement. initialize_available_filters and initialize_available_columns.
EasyQuery has two forms of appearance. First is default query which has static column cells. Second is called dynamic query.
Dynamic query allows user to edit values of columns within the table cell. Therefore, it has significant impact on the definition of columns.
For now Dynamic query supports only EasyIssueQuery, ProjectQuery and UserQuery.
initialize_available_filters
This method is used to define available filters for easy query. It is called only once during initialization of easy query.
To define filter, add_available_filter method is used. It takes two arguments - name of filter and options hash.
Name of filter is always same as name of column in database. Important option is :tpye.
It determines list of available operators as well as input field. Default type is :string. The :type also determines the necessary options.
For :list_autocomplete or tree is essential to define source of available values. It is achieved by options :source and :source_root.
Source defines method, usually managed by auto_completes_controller. Source root determines how individual objects will behave.
By :source_options is possible to define additional options, E.g.: project_id. If the filter is association (most of autocompletes are associations) and
name of column differs from class name, there is possibility to specify class by option :klass.
add_available_filter "collection_id",
type: :list_autocomplete,
source: "all_collections", source_root: "entities",
klass: ::Collection
Due to high amount of options, EasyQuery also provides specific method add_principal_autocomplete_filter for Principal descendants.
Method contains pre-defined options to avoid big chunks of similar code. It is best practice to use this method to define Principal filters.
For example: author_id, user_id, assignee_id, etc.
For :list or :list_optional is necessary to define list of possible values. It is achieved by option :values. Usually value of this option
is proc method, which retrieves defined values.
add_available_filter "status",
type: :list,
values: proc { queried_class.statuses.map { |key, value| [queried_class.human_enum_name("status", key), value] } }
initialize_available_columns
This method is used to define available columns for easy query. It is called only once during initialization of easy query.
To define column, add_column method is used. Method has three arguments name, type and options. Name defines name of column.
It should corresponds with entity property. For association is used name of association, not database_column. Type defines column cell behaviour.
It should correspond with the type of database column. In case of association or enum, :record is used.
Options are used to define additional column behaviour in both default and dynamic query.
For both type of queries, most used options are:
* sortable: Option that allows user to sort query by chosen column. Value of this option is chunk of SQL query.
Most common is table_name with name of database column. Occasionally may contains complex chunk of SQL query.
In case of multiple sorting criteria we use array of strings.
* default_order: Closely tight to sortable. Defines default ordering of specified column.
* groupable: Allows user to group query by values of specified column. Definition is similar to sortable.
Values of this options are also chunk of SQL containing table_name with name of database column.
If the defined easy query column name corresponds with table name and database column of queried entity, value true is possible to use.
* most_used: Self describing option of boolean kind. Default value is false.
* preload/includes: Define additional load of associated classes for joins purpose. Value is array of symbols of snake_case class names.
If sortable trough another table is defined, it is necessary to define includes for that association as well.
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 be able to edit columns of associations within dynamic query, autocomplete is required. This is achieved by specific options.
These options for dynamic query are:
* attribute: column name of association
* ref: referenced class
* source_options: additional options for autocomplete
* source: similar to :source in filters, method of possible values.
* source_root: similar to :source_root in filters, determines how individual objects will behave.
* params: similar to :source_options in filters, additional params for 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" }
Similar to filters, columns provides method add_principal_column. It is used to define columns of Principal descendants.
It is quiet new feature, so it is not used in many places. It is best practice to use this method to define Principal columns.
default_list_columns
Method purpose is obvious. Due to existence of query settings it prefers original method, but in case setting is missing it will fallback to this method. It content should be list of column names in string.
Scope Construction
Use entities_scope(options = {}) when callers need an ActiveRecord::Relation.
Use entities(options = {}) only when records should be materialized as an array.
Runtime scope assignment is public configuration; query-type defaults are protected subclass hooks:
| 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 generated filter SQL; returns a relation, proc, or nil. |
additional_statement / add_additional_statement |
default_additional_statement |
Trusted intrinsic SQL condition without WHERE; returns a string or nil. |
| — | additional_filter_statement |
Trusted SQL produced by custom filter composition; returns a string or nil. |
An assigned entity_scope normally replaces base_entity_scope, including any default
visibility it provided. The assigned relation must therefore already enforce applicable
authorization. Authorization-sensitive query types may explicitly override the public
entity_scope facade to intersect assignments with mandatory restrictions. For example,
Helpdesk retains workspace visibility when callers assign a scope. Such overrides must
only narrow visibility, never weaken it.
Assigning nil through additional_scope= explicitly suppresses
default_additional_scope. Call reset_additional_scope to remove the runtime override
and return to the subclass default. In contrast, a nil entity_scope= value falls
back to base_entity_scope.
Normal construction uses these private stages:
entities -> entities_scope
|-> build_entity_scope
\-> search_freetext -> build_entity_scope
build_entity_scope -> scope_with_required_associations
-> entity_scope + statement + additional_scope
build_entity_scope -> scope_options_with_order
build_entity_scope -> apply_scope_options
scope_with_required_associations applies generated filter SQL, the additional scope,
and required joins/preloads. build_entity_scope adds ordering and delegates relation
options such as conditions, grouping, limit, and offset to apply_scope_options.
scope_options_with_order prepares ordering before those stages.
create_entity_scope and new_entity_scope remain public only as deprecated
compatibility APIs. Prefer entities_scope. create_entity_scope bypasses free-search
dispatch and preserves historical group-order handling. new_entity_scope returns the
relation before ordering, pagination, and general scope options. Historically, when its
optional scope argument is supplied, that scope replaces both entity_scope and
generated statement; additional_scope and required associations are still applied.
This positional path also bypasses authorization-sensitive entity_scope facades, such
as Helpdesk's mandatory workspace intersection. The supplied relation must therefore
already enforce all authorization restrictions. This deprecated positional path is
higher risk. For normal runtime restriction, prefer entity_scope= or set_entity_scope
with entities_scope, while still supplying an authorization-filtered relation unless
the subclass facade explicitly intersects mandatory restrictions.
SQL statement APIs and hooks accept trusted internal SQL only. Never interpolate request
or user values into additional_statement, add_additional_statement,
default_additional_statement, or additional_filter_statement. Prefer relation/hash
conditions or bound-parameter arrays through scope APIs.
Free search limits results to 25 records by default. Pass limit: for an explicit
limit. Internal processing that intentionally needs an unbounded free-search relation
must pass skip_limit: true; do not use it for user-facing lists.
Subclass defaults belong 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
When migrating legacy subclass overrides, use this mapping:
entity_scope -> base_entity_scope
additional_scope -> default_additional_scope
additional_statement -> default_additional_statement
add_statement_sql_before_filters -> additional_filter_statement
Keep runtime writers and mutators public. Move only intrinsic subclass behavior to the protected hooks. If a legacy public facade enforced mandatory authorization, retain a public narrowing override rather than moving that security rule solely into a default hook.
EasyPage query methods
Any registered easy query can be used within EasyPage and dashboards. In use-case like that,
query can take multiple graph forms instead of table. To provide allowed forms for various queries,
each query implement methods to tell which forms specific query supports. Available forms (with default values) are:
* table/list - default: true,
* report - default: true,
* tiles - default: true,
* chart - default: false,
* calendar - default: false,
* reactive - default: false.
This methods are implemented for instance as well as class.
Easy Query registration
To be able to use easy query within dashboards, it is necessary to register it.
Query registration should be defined in one of initializers. For easy_engines, Easy plugins, and RYSes, use a file such as config/initializers/08_others.rb.
Registration should be defined in EasyInitHelper.on_constant_autoload("EasyQuery") so the registry remains lazy-loaded and reload-safe.
| config/initializers/08_others.rb | |
|---|---|
See EasyInitHelper registration helpers for the general registration rules.