-
Notifications
You must be signed in to change notification settings - Fork 330
Search Action Flow
By default, ActiveScaffold has the action module :search
, but it can be changed to :field_search
, using conf.actions.swap :search, :field_search
. Both modules use the action show_search
to open the form.
The search forms submit the data to the index
action.
These methods are called in the following order:
-
search_authorized_filter
called as before_action-
search_authorized?
(or the method defined in conf.search.link.security_method if it's changed) is called to check the permission. If this method returns false,search_authorized_filter
will raise ActiveScaffold::ActionNotAllowed.
-
-
show_search
-
respond_to_action
, which will call the corresponding response method for show_search action and the requested format.
-
Then it will render these views:
- search.html.erb (only for HTML request)
- _search.html.erb
There will be only a search string, and the query looks for it on every column in config.search.columns
, using column.search_sql
to build the conditions, joining all the comparisons with OR
. Search_sql
may be an array of column names or SQL code, e.g. using functions. The comparison will be made with LIKE
for text columns and =
for other columns, and depending on the value of config.search.text_search
will use wrap the text:
- with
%
if it's:full
, to match the text anywhere in the columns - appending
%
if it's:start
, to match the text at the beginning - prepending
%
if it's:end
, to match the text at the end - without adding any
%
, if it's any other value, to match the exact text
And depending on the value of config.search.split_terms
, it will look for the whole text, or will split the text using the value of split_terms
into tokens and look for each token, joining everything with AND
, so it requires to find every token, but each token may be found in different columns.
The items in search_sql
array can be a hash too, to use a subquery for the condition, with the following structure:
-
subquery
key, required, with an array:- the first element in the array must be the model or ActiveRecord relation used for the subquery. If the relation doesn't select one column for the subquery, the primary key will be used.
- the next elements are column names or SQL code, like normal items in
search_sql
, used for the conditions in the subquery
-
field
key, optional, the column name to match with the subquery result -
conditions
key, optional, an array with extra conditions, like the parameters forwhere
method from ActiveRecord.
For example, for a polymorphic association, in a Resume model:
conf.columns[:resume_holder].search_sql = [
{subquery: [User, 'first_name', 'last_name']},
{subquery: [Candidate, 'first_name', 'last_name']}
]
Then it would generate a condition when searching for 'John' like this:
resume_holder_id IN (SELECT id FROM users WHERE first_name LIKE '%John%' OR last_name LIKE '%John%') AND resume_holder_type = 'User' OR
resume_holder_id IN (SELECT id FROM candidates WHERE first_name LIKE '%John%' OR last_name LIKE '%John%') AND resume_holder_type = 'Candidate'
In that example, field
key is not needed as the foreign key for the column is used, and conditions
key is not needed because it's generated automatically to match the foreign type of the association. However, if using a virtual column, field will be needed, and conditions may be needed too, depending on the case:
conf.columns[:parent].search_sql = [
{subquery: [User, 'first_name', 'last_name'], field: :resume_holder_id, conditions: ['resume_holder_type = ?', 'User']},
{subquery: [Candidate, 'first_name', 'last_name'], field: :resume_holder_id, conditions: ['resume_holder_type = ?', 'Candidate']}
]
In the subquery
key, scopes or where can be used, e.g. User.where(disabled: false)
, or select(:column)
to use it instead of :id
.
These methods are called in the following order:
-
search_authorized_filter
called as before_action-
search_authorized?
(or the method defined in conf.field_search.link.security_method if it's changed) is called to check the permission. If this method returns false,search_authorized_filter
will raise ActiveScaffold::ActionNotAllowed.
-
-
show_search
-
respond_to_action
, which will call the corresponding response method for show_search action and the requested format.
-
Then it will render these views:
- field_search.html.erb (only for HTML request)
- _field_search.html.erb
Some of the columns defined in config.field_search.columns
may be rendered in a subgroup which is collapsible and hidden by default, adding them to config.field_search.optional_columns
too. When the form is open with a search active, they will be in the default group instead of the optional subgroup. The method visibles_and_hiddens
can be overrided in a helper module to change the columns which are hidden, e.g. depending on params used to load the list.
There will be different search params for each column, and each column may generate SQL condition in a different way. For each column in conf.field_search.columns
will call the class method condition_for_column
, which may use the following class methods:
-
condition_for_<column_name>_column
if exists, allowing to override how the conditions for a column are generated -
condition_for_<search_ui>_type
if exists, withsearch_ui
being thesearch_ui
of the column, or the column's type if no search_ui was defined. -
column.search_sql
if it's a Proc condition_for_search_ui
If the first method exists, it will be called and the value returned as SQL condition, otherwise, the value of one of the other 3 methods is expected to be a string used as SQL comparison (like the one used in where
rails method when using an array), and one or more values, and the SQL string is used as a template used to interpolate, replacing %<search_sql>s
or %{search_sql}
with each item in column.search_sql
, and joining all the conditions with OR
.
For example, an association column :user with these settings:
conf.columns[:user].search_ui = :string
conf.columns[:user].search_sql = ['users.name', 'users.email']
To generate conditions, with a search param {user: {from: 'John', opt: '?%'}}
, condition_for_search_ui
would be called, and would return ['%<search_sql>s LIKE ?', 'John%']
. Then, condition_for_column
would return ['users.name LIKE ? OR users.email LIKE ?', 'John%', 'John%']
.
Field_search module can be added without using swap, with conf.actions << :field_search
. ActiveScaffold don't support having 2 action links for the same action with the same parameters, so the action link for field_search should use the parameter kind
with the value :field_search
:
conf.field_search.link.parameters = {kind: :field_search}
Then show_search will render the view or partial for search module, and show_search with kind=field_search will render the view or partial for field_search module.
When index action is called, a before_action store_search_params_into_session
will read the params in :search
key, and delete them. If active_scaffold_config.store_user_settings
is enabled, search params are saved in the session, in active_scaffold_session_storage['search']
, otherwise search params are saved in @search_params
. Anyway, search params are accessible with the method search_params
, which can be used in controller, views and helpers. To access the search params for field_search
only, in case of using both modules, field_search_params
can be used, which will return search_params
if it's a Hash, or an empty Hash.
The index action will add conditions to the list query from the search params with the do_search
method, which is called with before_action too.