class Avo::SearchController
def apply_attach_scope(query, parent)
Parent passed as argument to be used as a variable instead of the method "def parent"
def apply_attach_scope(query, parent) # If the parent is nil it probably means that someone's creating the record so it's not attached yet. # In these scenarios, try to find the grandparent for the new views where the parent is nil # and initialize the parent record with the grandparent attached so the user has the required information # to scope the query. # Example usage: Got to a project, create a new review, and search for a user. if parent.blank? && params[:via_parent_resource_id].present? && params[:via_parent_resource_class].present? && params[:via_relation].present? parent_resource_class = BaseResource.get_model_by_name params[:via_parent_resource_class] reflection_class = BaseResource.get_model_by_name params[:via_reflection_class] grandparent = parent_resource_class.find params[:via_parent_resource_id] parent = reflection_class.new( params[:via_relation] => grandparent ) end Avo::ExecutionContext.new(target: attach_scope, query: query, parent: parent).handle end
def apply_has_many_scope
def apply_has_many_scope association_name = BaseResource.valid_association_name(parent, params[:via_association_id]) # Get association records query = parent.send(association_name) # Apply policy scope if authorization is present query = resource.authorization&.apply_policy query Avo::ExecutionContext.new(target: @resource.class.search_query, params: params, query: query).handle end
def apply_scope(query)
When searching in a `has_many` association and will scope out the records against the parent record.
def apply_scope(query) if should_apply_has_many_scope? apply_has_many_scope elsif should_apply_attach_scope? apply_attach_scope(query, parent) end end
def apply_search_metadata(records, avo_resource)
def apply_search_metadata(records, avo_resource) records.map do |record| resource = avo_resource.new(record: record) fetch_result_information record, resource, resource.class.fetch_search(:item, record: record) end end
def attach_scope
def attach_scope @attach_scope ||= field&.attach_scope end
def error_payload(_label:, _url: "")
def error_payload(_label:, _url: "") { header: "🚨 An error occurred during search 🚨", help: "Please review and resolve the issue before deployment 🚨", results: { _label:, _url:, _error: true }, count: 1 } end
def fetch_field
def fetch_field return if params[:via_association_id].nil? reflection_resource = Avo.resource_manager.get_resource_by_model_class(params[:via_reflection_class]).new( view: Avo::ViewInquirer.new(params[:via_reflection_view]), record: parent, params: params, user: _current_user ) reflection_resource.detect_fields.get_field(params[:via_association_id]) end
def fetch_parent
def fetch_parent return unless params[:via_reflection_id].present? parent_resource = Avo.resource_manager.get_resource_by_model_class params[:via_reflection_class] parent_resource.find_record params[:via_reflection_id], params: params end
def fetch_result_information(record, resource, item)
def fetch_result_information(record, resource, item) title = item&.dig(:title) || resource.record_title highlighted_title = highlight(title&.to_s, CGI.escapeHTML(params[:q] || "")) record_path = if resource.link_to_child_resource Avo.resource_manager.get_resource_by_model_class(record.class).new(record: record).record_path else resource.record_path end { _id: record.to_param, _label: highlighted_title, _url: resource.class.fetch_search(:result_path, record: resource.record) || record_path } end
def field
def field @field ||= fetch_field end
def parent
def parent @parent ||= fetch_parent end
def parse_results(query, resource)
def parse_results(query, resource) # When using custom search services query should return an array of hashes if query.is_a?(Array) # Apply highlight query.map do |result| result[:_label] = highlight(result[:_label].to_s, CGI.escapeHTML(params[:q] || "")) end # Force count to 0 until implement an API to pass the count results_count = 0 # Apply the limit results = query.first(search_results_count(resource)) else query = apply_scope(query) if should_apply_any_scope? # Get the count results_count = query.reselect(resource.model_class.primary_key).count # Get the results query = query.limit(search_results_count(resource)) results = apply_search_metadata(query, resource) end [results_count, results] end
def process_results(results, request:)
def process_results(results, request:) results end
def render_error(...)
def render_error(...) raise error unless render_error? render json: { error: error_payload(...) }, status: 500 end
def render_error?
def render_error? Rails.env.development? end
def search_resource(resource)
def search_resource(resource) key = resource.name.pluralize.downcase # If search query is not defined return error in dev and nil otwherwise. if resource.search_query.blank? return nil unless render_error? search_query_undefined = error_payload( _label: "Please configure the search for #{resource}", _url: "https://docs.avohq.io/3.0/search.html#enable-search-for-a-resource" ) return [key, search_query_undefined] end query = Avo::ExecutionContext.new( target: resource.search_query, params: params, query: resource.query_scope ).handle results_count, results = parse_results(query, resource) header = resource.plural_name if results_count > 0 header = "#{header} (#{results_count})" end result_object = { header: header, help: resource.fetch_search(:help) || "", results: results, count: results.count } [key, result_object] end
def search_resources(resources, request: nil)
def search_resources(resources, request: nil) process_results search_results(resources, request:), request: end
def search_results(resources, request: nil)
def search_results(resources, request: nil) resources .map do |resource| # Apply authorization next unless @authorization.set_record(resource.model_class).authorize_action( :search, policy_class: resource.authorization_policy, raise_exception: false ) search_resource resource end .select do |payload| payload.present? end .sort_by do |payload| payload.last[:count] end .reverse .to_h end
def search_results_count(resource)
def search_results_count(resource) if resource.search_results_count Avo::ExecutionContext.new( target: resource.search_results_count, params: params ).handle else Avo.configuration.search_results_count end end
def should_apply_any_scope?
def should_apply_any_scope? should_apply_has_many_scope? || should_apply_attach_scope? end
def should_apply_attach_scope?
def should_apply_attach_scope? params[:via_association] == "belongs_to" && attach_scope.present? end
def should_apply_has_many_scope?
def should_apply_has_many_scope? params[:via_association] == "has_many" && @resource.class.search_query.present? end
def show
def show render json: search_resources([resource], request:) rescue => error render_error _label: error.message end