Class: Search
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Search
- Defined in:
- app/models/search.rb
Overview
Single-table-inheritance base class for every saved-query type in
the CRM (e.g. CustomerSearch, OrderSearch, etc.). Holds the user-
editable bundle of query params, selected columns, sort, pagination,
and pinning, and exposes a Ransack-backed query result via
QueryTemplate.
Subclasses configure themselves with default_selected_columns,
default_sort_columns, and composite_columns so each searchable
entity stays in sync with its underlying ActiveRecord model.
Direct Known Subclasses
ActivitySearch, BudgetSearch, CertificationSearch, ContactSearch, CouponSearch, CreditApplicationSearch, CreditMemoSearch, CustomerSearch, DeliverySearch, EmployeeReviewSearch, ExchangeRateSearch, InvoiceSearch, ItAssetSearch, ItemLedgerEntrySearch, ItemSearch, LedgerEntrySearch, LocatorRecordSearch, OpportunitySearch, OrderSearch, OutgoingPaymentSearch, ProductCatalogSearch, PurchaseOrderSearch, QuoteSearch, ReceiptSearch, RmaSearch, RoomConfigurationSearch, SalesCommissionSearch, ServiceJobSearch, SupplierItemSearch, SupportCaseSearch, VoucherSearch
Defined Under Namespace
Classes: ActivityPresenter, ActivityTextPresenter, BudgetPresenter, CertificationPresenter, ContactPresenter, ContactTextPresenter, CouponPresenter, CouponTextPresenter, CreditApplicationPresenter, CreditApplicationTextPresenter, CreditMemoPresenter, CustomerPresenter, CustomerTextPresenter, DeliveryPresenter, DeliveryTextPresenter, EmployeeReviewPresenter, EmployeeReviewTextPresenter, ExchangeRatePresenter, InvoicePresenter, InvoiceSummaryPresenter, InvoiceTextPresenter, ItAssetPresenter, ItemLedgerEntryPresenter, ItemLedgerEntryTextPresenter, ItemPresenter, ItemTextPresenter, LedgerEntryPresenter, LedgerEntryTextPresenter, LocatorRecordPresenter, LocatorRecordTextPresenter, OpportunityPresenter, OpportunitySummaryPresenter, OpportunityTextPresenter, OrderPresenter, OrderTextPresenter, OutgoingPaymentPresenter, PresenterFactory, PresetJobPresenter, PresetJobTextPresenter, ProductCatalogPresenter, ProductCatalogTextPresenter, PurchaseOrderPresenter, PurchaseOrderTextPresenter, QuotePresenter, QuoteTextPresenter, ReceiptPresenter, RmaPresenter, RmaTextPresenter, RoomConfigurationPresenter, RoomConfigurationTextPresenter, SalesCommissionPresenter, ServiceJobPresenter, SupplierItemPresenter, SupplierItemTextPresenter, SupportCasePresenter, SupportCaseTextPresenter, VoucherPresenter
Constant Summary collapse
- DISTANCE_SEARCH_KEYS =
Keys consumed by apply_distance_search, apply_customer_cross_reference, and geocoding
in the base class — excluded from the Ransack strict probe across all subclasses. %w[target_coordinates within_miles within_miles_of cross_reference_customer_search_id].freeze
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Class Attribute Summary collapse
-
.composite_columns ⇒ Object
readonly
Returns the value of attribute composite_columns.
-
.default_selected_columns ⇒ Object
readonly
Returns the value of attribute default_selected_columns.
-
.default_sort_columns ⇒ Object
readonly
Returns the value of attribute default_sort_columns.
-
.global_favorites ⇒ Object
readonly
Returns the value of attribute global_favorites.
Instance Attribute Summary collapse
-
#composite_columns ⇒ Object
readonly
Returns the value of attribute composite_columns.
-
#pagy_count ⇒ Object
readonly
Expose raw pagination metadata for the controller to build a proper Pagy object with request context.
-
#pagy_limit ⇒ Object
readonly
Expose raw pagination metadata for the controller to build a proper Pagy object with request context.
-
#pagy_page ⇒ Object
readonly
Expose raw pagination metadata for the controller to build a proper Pagy object with request context.
-
#sql_select_columns ⇒ Object
readonly
Returns the value of attribute sql_select_columns.
Belongs to collapse
Has many collapse
Delegated Instance Attributes collapse
-
#view_resource_klass ⇒ Object
Alias for Class#view_resource_klass.
Class Method Summary collapse
- .allowed_role_ids ⇒ Object
-
.available_output_columns ⇒ Object
Return a list of available output columns combining composite columns and actual view defined columns.
-
.base_search_class_name ⇒ Object
Returns a base model view of what we're trying to earch on.
-
.composite_column(*options) ⇒ Hash
Sets 'virtual' attribute or composite column which will be rendered in the presenter but require specific columns present in the select, define these at top of the search class such as customer: { select: [:customer_full_name, :customer_id]} in the example above we can specify customer as an output column which will translate in customer_id and customer_full_name being added to the select for the query.
- .custom_criteria_keys ⇒ Object
-
.database_columns ⇒ Object
Returns an array of database level columns names as symbols, these are defined by the view resource class and how its sql view is defined.
-
.default_columns(*columns) ⇒ Object
Sets the default columns to be used when creating this search use default_columns :id, :name, ...
-
.default_sort(*columns) ⇒ Object
Sets the default sort to be used when creating this search use default_columns :id, :name, ...
-
.favorites ⇒ ActiveRecord::Relation<Search>
A relation of Searches that are favorites.
- .friendly_column_name(column_name) ⇒ Object
-
.global_favorite(*options) ⇒ Hash
Registers a global favorite (saved query template) for this search class.
- .has_role_for_search?(role_ids) ⇒ Boolean
- .instantiate_query_template(template_key = nil, extra_options = nil) ⇒ Object
- .main_resource_class ⇒ Object
- .main_resource_table ⇒ Object
- .mass_actions_for_select ⇒ Object
-
.maximum_unpersisted_queries ⇒ Object
Actions that must run synchronously in the controller because they render.
- .options(_visible_only = true) ⇒ Object
- .options_classes ⇒ Object
- .query_favorites_templates ⇒ Object
-
.recent_searches_for_employee_id ⇒ ActiveRecord::Relation<Search>
A relation of Searches that are recent searches for employee id.
- .search_name ⇒ Object
- .search_type ⇒ Object
- .search_type_humanized ⇒ Object
-
.select_sort_columns ⇒ Object
Returns available columns for sorting.
- .unpersisted_queries_quote_reached?(search_type, employee_id) ⇒ Boolean
- .view_resource_class ⇒ Object
- .view_resource_klass ⇒ Object
- .view_resource_table ⇒ Object
- .visible? ⇒ Boolean
-
.with_search_results ⇒ ActiveRecord::Relation<Search>
A relation of Searches that are with search results.
Instance Method Summary collapse
-
#all_composite_columns ⇒ Object
Returns a combination of instance level and class level composite columns instance levels are defined in the query build mechanism and unit to this search instance.
-
#append_custom_column(*options) ⇒ void
Sometimes the criteria will append a custom field to the query that is not part of the initial view, we need to make this field part of the output and also the custom select pass options to this function as you would composite_column, e.g :distance, select: '(select max(x) as distance)'.
- #append_ouput_column(column_name) ⇒ Object
- #append_to_sql_select_columns(*args) ⇒ Object protected
-
#applicable_sort_terms ⇒ Object
Filters the saved sort columns down to those that are in the query output and can be expressed as a SQL ORDER BY term (see #sortable_column?).
-
#apply_array_match(results, param, column = nil, nil_value = nil, any_value = nil) ⇒ Object
protected
param: an array of values to test against nil_value: of the array of values, which one represent a database null, e.g.
- #apply_criteria ⇒ Object
-
#apply_customer_cross_reference(results, join_key = nil) ⇒ Object
Apply customer cross reference.
-
#apply_distance_search(results, options = {}) ⇒ ActiveRecord::Relation
Restricts the results to rows within the queried distance of the target coordinates, adding a distance column.
- #apply_select(results) ⇒ Object
-
#apply_sort(results) ⇒ Object
Apply sort columns to a result set, and transforms composite column to their consituents.
-
#available_events ⇒ Object
Assuming our resources implement state machines, retrieves a list of available events that can be triggered.
-
#available_output_columns ⇒ Object
Instance level available output column check, this takes into consideration Dynamic attributes added as the select query is built.
- #cleanup_search_results ⇒ Object protected
-
#composite_column(*options) ⇒ Hash
Similar to class version but affects the instance variable list of composite columns.
- #discard_excess ⇒ Object protected
- #effective_name ⇒ Object
-
#effective_short_name ⇒ Object
def create_custom_column(results, column_name, type) pgc = ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(column_name, nil, type) results.columns << pgc unless results.columns.include?(pgc) results.column_names << column_name unless results.column_names.include?(column_name) end.
-
#enqueue_navbar_pinned_refresh ⇒ Object
Schedules a single coalesced Turbo Stream replace of this employee's pinned-items navbar badge.
- #fast_count ⇒ Object
- #get_pinned_results ⇒ Object
-
#get_sort_term(column_name, sort_direction = 'ASC') ⇒ Object
Generate the SQL sort term for a given column, it checks if the column name is a composite column with a custom select, in which case replace the sort term.
- #human_query_params ⇒ Object
- #instantiate_resource_updater(params = {}) ⇒ Object protected
-
#limit_options ⇒ Object
if you want to override the available result set size you can do so in your specialized class.
-
#mass_export(params = {}, cur_user = nil, path = nil) ⇒ Object
Mass Export.
-
#normalize_for_mass_update(_resource, _params) ⇒ Object
Per-record hook called by SearchResourceUpdateWorker before saving each resource.
-
#output_columns_for_select ⇒ Object
Renders a select list of possible output columns including all composite columns, note that we only include class level, permanent composite columns and not instance level which can disapear.
-
#pagy_from_search ⇒ Object
Backward-compatible reader: returns a simple struct with count/page/limit so existing controller code (pagy(:offset, results, count:, page:, limit:)) keeps working unchanged.
- #perform(page = 1, sort = nil, direction = nil, record_results = false, per_page = nil, autosave = true, disable_pagination = false) ⇒ Object
- #perform_selected(selected_columns: nil) ⇒ Object
-
#pinned_query ⇒ Object
Retrieves the pinned search results, in most case this is suffucient but for speed purpose you might want to override and specialize e.g.
-
#prepend_custom_column(*options) ⇒ void
Same as append_custom_column except it adds it to the front of the column list.
- #prepend_output_column(column_name) ⇒ Object
-
#ransack_probe_params ⇒ Object
Returns the query_params hash with non-Ransack keys stripped for the strict probe.
- #record_list(resource_ids) ⇒ Object
-
#refresh_pinned_results ⇒ Object
Re-runs the saved search criteria as a prune over the user's curated pin selection: keeps the intersection of (currently-pinned IDs) and (records that still match the criteria), drops the rest.
- #remove_other_pins ⇒ Object protected
- #search_name ⇒ Object
-
#select_sort_columns ⇒ Object
Instance level list of possible sort columns.
- #select_statement ⇒ Object protected
- #set_defaults ⇒ Object
-
#set_sort_term(set_sort_column, set_sort_direction = nil) ⇒ Object
Given a single sort column applies it and overrides any previous search ordering.
-
#sort_column1 ⇒ Object
BEGIN INSTANCE VARIABLES #.
- #sort_column1=(val) ⇒ Object
- #sort_column2 ⇒ Object
- #sort_column2=(val) ⇒ Object
- #sort_column3 ⇒ Object
- #sort_column3=(val) ⇒ Object
-
#sort_columns_for_select ⇒ Object
Retrieves a list of sort columns suitable for a select.
-
#sort_columns_to_hash ⇒ Object
Takes all the sort columns specified for a search and returns them as a hash with a symbolized key being the column name and the value being the sort direction e.g.
-
#sortable_column?(column_name) ⇒ Boolean
Whether +column_name+ can appear in a SQL ORDER BY clause.
- #target_geocoding ⇒ Object protected
- #unrecord_list(resource_ids) ⇒ Object
-
#validated_selected_columns ⇒ Object
Presentation layer on top of store array attribute selected columns, makes sure to symbolize keys, prevents duplicate, cleans up blanks.
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Class Attribute Details
.composite_columns ⇒ Object (readonly)
Returns the value of attribute composite_columns.
41 42 43 |
# File 'app/models/search.rb', line 41 def composite_columns @composite_columns end |
.default_selected_columns ⇒ Object (readonly)
Returns the value of attribute default_selected_columns.
41 42 43 |
# File 'app/models/search.rb', line 41 def default_selected_columns @default_selected_columns end |
.default_sort_columns ⇒ Object (readonly)
Returns the value of attribute default_sort_columns.
41 42 43 |
# File 'app/models/search.rb', line 41 def default_sort_columns @default_sort_columns end |
.global_favorites ⇒ Object (readonly)
Returns the value of attribute global_favorites.
41 42 43 |
# File 'app/models/search.rb', line 41 def global_favorites @global_favorites end |
Instance Attribute Details
#composite_columns ⇒ Object (readonly)
Returns the value of attribute composite_columns.
48 49 50 |
# File 'app/models/search.rb', line 48 def composite_columns @composite_columns end |
#pagy_count ⇒ Object (readonly)
Expose raw pagination metadata for the controller to build a proper Pagy object with request context
50 51 52 |
# File 'app/models/search.rb', line 50 def pagy_count @pagy_count end |
#pagy_limit ⇒ Object (readonly)
Expose raw pagination metadata for the controller to build a proper Pagy object with request context
50 51 52 |
# File 'app/models/search.rb', line 50 def pagy_limit @pagy_limit end |
#pagy_page ⇒ Object (readonly)
Expose raw pagination metadata for the controller to build a proper Pagy object with request context
50 51 52 |
# File 'app/models/search.rb', line 50 def pagy_page @pagy_page end |
#sql_select_columns ⇒ Object (readonly)
Returns the value of attribute sql_select_columns.
48 49 50 |
# File 'app/models/search.rb', line 48 def sql_select_columns @sql_select_columns end |
Class Method Details
.allowed_role_ids ⇒ Object
85 86 87 |
# File 'app/models/search.rb', line 85 def self.allowed_role_ids nil end |
.available_output_columns ⇒ Object
Return a list of available output columns combining composite columns
and actual view defined columns. This is a class level method
that does not take into consideration dynamically added fields
which only exist in the context of a query instance
230 231 232 |
# File 'app/models/search.rb', line 230 def self.available_output_columns (composite_columns || {}).keys | database_columns end |
.base_search_class_name ⇒ Object
Returns a base model view of what we're trying to earch on. e.g Order for OrderSearch
241 242 243 |
# File 'app/models/search.rb', line 241 def self.base_search_class_name name.scan(/(\w+)Search/).join end |
.composite_column(*options) ⇒ Hash
Sets 'virtual' attribute or composite column which will be
rendered in the presenter but require specific columns present
in the select, define these at top of the search class such as
customer: { select: [:customer_full_name, :customer_id]}
in the example above we can specify customer as an output column
which will translate in customer_id and customer_full_name being added
to the select for the query
221 222 223 224 |
# File 'app/models/search.rb', line 221 def self.composite_column(*) @composite_columns ||= {} @composite_columns[.shift] = ..dup end |
.custom_criteria_keys ⇒ Object
491 492 493 |
# File 'app/models/search.rb', line 491 def self.custom_criteria_keys [] end |
.database_columns ⇒ Object
Returns an array of database level columns names as symbols, these are defined by the view resource class
and how its sql view is defined
236 237 238 |
# File 'app/models/search.rb', line 236 def self.database_columns view_resource_klass.column_names.map(&:to_sym) end |
.default_columns(*columns) ⇒ Object
Sets the default columns to be used when creating this search
use default_columns :id, :name, ... etc at head of search class
204 205 206 |
# File 'app/models/search.rb', line 204 def self.default_columns(*columns) @default_selected_columns = columns end |
.default_sort(*columns) ⇒ Object
Sets the default sort to be used when creating this search
use default_columns :id, :name, ... etc at head of search class
198 199 200 |
# File 'app/models/search.rb', line 198 def self.default_sort(*columns) @default_sort_columns = columns end |
.favorites ⇒ ActiveRecord::Relation<Search>
A relation of Searches that are favorites. Active Record Scope
80 |
# File 'app/models/search.rb', line 80 scope :favorites, -> { where(persist: true) } |
.friendly_column_name(column_name) ⇒ Object
173 174 175 |
# File 'app/models/search.rb', line 173 def self.friendly_column_name(column_name) view_resource_klass.human_attribute_name(column_name) end |
.global_favorite(*options) ⇒ Hash
Registers a global favorite (saved query template) for this search
class.
191 192 193 194 |
# File 'app/models/search.rb', line 191 def self.global_favorite(*) @global_favorites ||= {} @global_favorites[.shift] = ..dup end |
.has_role_for_search?(role_ids) ⇒ Boolean
89 90 91 92 93 |
# File 'app/models/search.rb', line 89 def self.has_role_for_search?(role_ids) return true if allowed_role_ids.nil? role_ids.intersect?(allowed_role_ids) end |
.instantiate_query_template(template_key = nil, extra_options = nil) ⇒ Object
132 133 134 135 136 |
# File 'app/models/search.rb', line 132 def self.instantiate_query_template(template_key = nil, = nil) params = global_favorites.try(:[], template_key) || {} params = params.deep_merge() if QueryTemplate.new(self, params) end |
.main_resource_class ⇒ Object
148 149 150 |
# File 'app/models/search.rb', line 148 def self.main_resource_class name.match(/(.+)Search/)[1] end |
.main_resource_table ⇒ Object
152 153 154 |
# File 'app/models/search.rb', line 152 def self.main_resource_table main_resource_class.tableize end |
.mass_actions_for_select ⇒ Object
118 119 120 121 |
# File 'app/models/search.rb', line 118 def self.mass_actions_for_select opts = instance_methods + superclass.instance_methods opts.grep(/^mass_/).sort.map { |m| [m.to_s.humanize, m] }.uniq end |
.maximum_unpersisted_queries ⇒ Object
Actions that must run synchronously in the controller because they render
124 125 126 |
# File 'app/models/search.rb', line 124 def self.maximum_unpersisted_queries 5 end |
.options(_visible_only = true) ⇒ Object
95 96 97 |
# File 'app/models/search.rb', line 95 def self.(_visible_only = true) .map { |search_klass| [search_klass.search_name, search_klass.name] } end |
.options_classes ⇒ Object
99 100 101 102 103 104 |
# File 'app/models/search.rb', line 99 def self. list = %i[activity_search contact_search customer_search delivery_search employee_review_search sales_commission_search invoice_search it_asset_search item_search product_catalog_search item_ledger_entry_search ledger_entry_search supplier_item_search locator_record_search opportunity_search order_search purchase_order_search credit_memo_search quote_search rma_search room_configuration_search support_case_search credit_application_search coupon_search voucher_search outgoing_payment_search receipt_search certification_search] list.sort.map { |scn| scn.to_s.classify.constantize } end |
.query_favorites_templates ⇒ Object
138 139 140 141 142 |
# File 'app/models/search.rb', line 138 def self.query_favorites_templates (global_favorites || {}).map do |_k, | QueryTemplate.new(self, ) end end |
.recent_searches_for_employee_id ⇒ ActiveRecord::Relation<Search>
A relation of Searches that are recent searches for employee id. Active Record Scope
82 |
# File 'app/models/search.rb', line 82 scope :recent_searches_for_employee_id, ->(employee_id) { where(employee_id: employee_id).order(created_at: :desc) } |
.search_name ⇒ Object
114 115 116 |
# File 'app/models/search.rb', line 114 def self.search_name name.titleize end |
.search_type ⇒ Object
106 107 108 |
# File 'app/models/search.rb', line 106 def self.search_type name.tableize.singularize end |
.search_type_humanized ⇒ Object
110 111 112 |
# File 'app/models/search.rb', line 110 def self.search_type_humanized search_type.humanize.titleize end |
.select_sort_columns ⇒ Object
Returns available columns for sorting
169 170 171 |
# File 'app/models/search.rb', line 169 def self.select_sort_columns view_resource_klass.select_sort_columns end |
.unpersisted_queries_quote_reached?(search_type, employee_id) ⇒ Boolean
128 129 130 |
# File 'app/models/search.rb', line 128 def self.unpersisted_queries_quote_reached?(search_type, employee_id) Search.where(employee_id: employee_id).where(type: search_type).where('persist IS NULL or NOT persist').count >= maximum_unpersisted_queries end |
.view_resource_class ⇒ Object
156 157 158 |
# File 'app/models/search.rb', line 156 def self.view_resource_class "View#{name.match(/(.+)Search/)[1]}" end |
.view_resource_klass ⇒ Object
160 161 162 |
# File 'app/models/search.rb', line 160 def self.view_resource_klass view_resource_class.constantize end |
.view_resource_table ⇒ Object
164 165 166 |
# File 'app/models/search.rb', line 164 def self.view_resource_table view_resource_class.tableize end |
.visible? ⇒ Boolean
144 145 146 |
# File 'app/models/search.rb', line 144 def self.visible? true end |
.with_search_results ⇒ ActiveRecord::Relation<Search>
A relation of Searches that are with search results. Active Record Scope
81 |
# File 'app/models/search.rb', line 81 scope :with_search_results, -> { where('exists(select 1 from search_results sr where sr.search_id = searches.id)') } |
Instance Method Details
#all_composite_columns ⇒ Object
Returns a combination of instance level and class level composite columns
instance levels are defined in the query build mechanism and unit to this
search instance. class level are defined in the search class header via composite_column:
308 309 310 |
# File 'app/models/search.rb', line 308 def all_composite_columns (self.class.composite_columns || {}).merge(composite_columns || {}) end |
#append_custom_column(*options) ⇒ void
This method returns an undefined value.
Sometimes the criteria will append a custom field to the query that is
not part of the initial view, we need to make this field part of the output
and also the custom select
pass options to this function as you would composite_column, e.g
:distance, select: '(select max(x) as distance)'
346 347 348 349 350 |
# File 'app/models/search.rb', line 346 def append_custom_column(*) column_name = .first.to_s composite_column(*) append_ouput_column(column_name) end |
#append_ouput_column(column_name) ⇒ Object
364 365 366 367 |
# File 'app/models/search.rb', line 364 def append_ouput_column(column_name) selected_columns.delete(column_name) selected_columns << column_name end |
#append_to_sql_select_columns(*args) ⇒ Object (protected)
954 955 956 |
# File 'app/models/search.rb', line 954 def append_to_sql_select_columns(*args) @sql_select_columns += args end |
#applicable_sort_terms ⇒ Object
Filters the saved sort columns down to those that are in the query output
and can be expressed as a SQL ORDER BY term (see #sortable_column?). The
direction is allow-listed to ASC/DESC before it reaches the raw Arel.sql
ORDER BY in #apply_sort; anything else (or a missing direction) defaults to
ASC, so a crafted ?direction= param can't be interpolated into the query.
667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 |
# File 'app/models/search.rb', line 667 def applicable_sort_terms sorts = [] sort_columns.each do |sort_column_combined| sort_column_fq, sort_order = sort_column_combined.split # Sort columns can be fully qualified (table_name.sort_column) here we # detect this and assign accordingly sort_column, = sort_column_fq.split('.').reverse # available_output_columns never contains the fully qualified version # So we check against the sort_column name next unless available_output_columns.include?(sort_column.to_sym) && sortable_column?(sort_column) direction = sort_order.to_s.upcase direction = 'ASC' unless %w[ASC DESC].include?(direction) sorts << [sort_column_fq, direction] end sorts end |
#apply_array_match(results, param, column = nil, nil_value = nil, any_value = nil) ⇒ Object (protected)
param: an array of values to test against
nil_value: of the array of values, which one represent a database null, e.g. "Open" might translate to records having result null
any_value: of the array of values, which means 'any value', note that this trumps nil_value
931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 |
# File 'app/models/search.rb', line 931 def apply_array_match(results, param, column = nil, nil_value = nil, any_value = nil) if query_params[param].present? column ||= param val_ids = query_params[param].dup.compact.uniq results.unscoped # Start with a blank # Any value just checks for non nil end of story if any_value && val_ids.include?(any_value) results = results.where.not(column => nil) else # nil_value might be a string expressing null so we substitute, e.g. "not blank" val_ids << nil if nil_value && val_ids.delete(nil_value) # Block given we might add more values to check on val_ids = yield(val_ids) if block_given? results = results.where(column => val_ids) if val_ids.present? end end results end |
#apply_criteria ⇒ Object
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 |
# File 'app/models/search.rb', line 464 def apply_criteria criteria_klass = self.class.view_resource_klass # Strict probe: build the search object (no SQL) with unknown conditions # disallowed. If any stored param key is stale/renamed, Ransack raises # InvalidSearchError. We report it to AppSignal and continue — the real # query below uses the default lenient mode so the user is never disrupted. # Custom keys handled by apply_custom_criteria and apply_distance_search are # excluded from the probe to avoid false positives. begin probe_params = ransack_probe_params criteria_klass.ransack(probe_params, ignore_unknown_conditions: false) rescue Ransack::InvalidSearchError => e Appsignal.report_error(e) do |transaction| transaction.( search_type: self.class.name, search_id: id.to_s, query_params: query_params.to_json ) end end results = criteria_klass.ransack(query_params).result results = apply_custom_criteria(results) if respond_to?(:apply_custom_criteria) results end |
#apply_customer_cross_reference(results, join_key = nil) ⇒ Object
Apply customer cross reference
529 530 531 532 533 534 535 536 537 538 |
# File 'app/models/search.rb', line 529 def apply_customer_cross_reference(results, join_key = nil) join_key ||= 'customer_id' fq_join_key = "#{self.class.view_resource_table}.#{join_key}" if query_params[:cross_reference_customer_search_id].present? results = results.where( "EXISTS(select 1 from search_results src WHERE src.resource_type = 'Customer' and src.resource_id = #{fq_join_key} and src.search_id IN (?))", [query_params[:cross_reference_customer_search_id]].flatten.join(',') ) end results end |
#apply_distance_search(results, options = {}) ⇒ ActiveRecord::Relation
Restricts the results to rows within the queried distance of the
target coordinates, adding a distance column.
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 |
# File 'app/models/search.rb', line 552 def apply_distance_search(results, = {}) custom_table_join = [:custom_table_join] custom_table_alias = [:custom_table_alias] target_coordinates = query_params[:target_coordinates] within_miles = query_params[:within_miles].present? ? query_params[:within_miles].to_i : 25 # custom_table_join ||= "INNER JOIN addresses on addresses.id = address_id" # custom_table_alias ||= "addresses" lat_column = [:lat_column] || 'lat' lng_column = [:lng_column] || 'lng' lat_column = "#{custom_table_alias}.#{lat_column}" if custom_table_alias lng_column = "#{custom_table_alias}.#{lng_column}" if custom_table_alias lat, lng = target_coordinates if target_coordinates.present? && lat && lng origin_lat = Math.deg2rad(lat) origin_lng = Math.deg2rad(lng) distance_formula = <<-EOS (ACOS(COS(#{origin_lat})*COS(#{origin_lng})*COS(RADIANS(#{lat_column}))*COS(RADIANS(#{lng_column}))+ COS(#{origin_lat})*SIN(#{origin_lng})*COS(RADIANS(#{lat_column}))*SIN(RADIANS(#{lng_column}))+ SIN(#{origin_lat})*SIN(RADIANS(#{lat_column})))*3963) EOS results = results.joins(custom_table_join) prepend_custom_column(:distance, raw: "#{distance_formula} as distance", data_type: :float) results = results.where("(#{distance_formula}) < ?", within_miles) set_sort_term(:distance) if sort_columns.blank? else # reset because it won't be there sort_columns.delete('distance') end results end |
#apply_select(results) ⇒ Object
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
# File 'app/models/search.rb', line 501 def apply_select(results) # Builds an array of sql select statements based on an array of column name, translating any # composite key to their required actual column sql_select_columns = [] sql_select_columns << if new_record? 'false as selected_attr' else "(EXISTS(select id from search_results sr where sr.resource_id = #{self.class.view_resource_table}.id and sr.search_id = #{id})) as selected_attr" end columns = [:id] validated_selected_columns.each do |column| if (composite_attrs = all_composite_columns[column.to_sym]) sql_select_columns |= [composite_attrs[:raw]].flatten.compact columns |= [composite_attrs[:select]].flatten.compact # create_custom_column(results, column.to_s, composite_attrs[:data_type]) elsif self.class.view_resource_klass.column_names.include?(column.to_s) columns |= [column] else # column does not exist, remove selected_columns.delete(column.to_s) end end sql_select_columns += columns.compact.uniq.map { |column| "#{self.class.view_resource_table}.#{column}" } results.select(sql_select_columns.join(',')) end |
#apply_sort(results) ⇒ Object
Apply sort columns to a result set, and transforms composite column to their
consituents
687 688 689 690 691 692 693 694 695 696 697 698 699 |
# File 'app/models/search.rb', line 687 def apply_sort(results) return results if sort_columns.blank? sorts = applicable_sort_terms if sorts.present? sort_sql = Arel.sql(sorts.map { |(column_name, sort_direction)| get_sort_term(column_name, sort_direction) }.join(',')) results = results.reorder(sort_sql) else # Auto fix bad sort columns update_column(:sort_columns, []) end results end |
#available_events ⇒ Object
Assuming our resources implement state machines, retrieves a list of available events that can be triggered
896 897 898 899 900 901 902 |
# File 'app/models/search.rb', line 896 def available_events events = [] search_results.includes(:resource).filter_map(&:resource).uniq.each do |resource| events += resource.state_events.select { |evt| resource.send(:"can_#{evt}?") } end events.compact.uniq.sort.map { |evt| [evt.to_s.titleize, evt] } end |
#available_output_columns ⇒ Object
Instance level available output column check, this takes into consideration
Dynamic attributes added as the select query is built
286 287 288 |
# File 'app/models/search.rb', line 286 def available_output_columns (composite_columns || {}).keys | self.class.available_output_columns end |
#cleanup_search_results ⇒ Object (protected)
962 963 964 |
# File 'app/models/search.rb', line 962 def cleanup_search_results search_results.delete_all if saved_change_to_query_params? end |
#composite_column(*options) ⇒ Hash
Similar to class version but affects the instance variable list of composite
columns. When the query is built both instance and class level composite
columns are combined. THis allows for instance dynamic attributes to be added
such as a distance column when proximity critiera are passed in
300 301 302 303 |
# File 'app/models/search.rb', line 300 def composite_column(*) @composite_columns ||= {} @composite_columns[.shift] = ..dup end |
#discard_excess ⇒ Object (protected)
972 973 974 975 |
# File 'app/models/search.rb', line 972 def discard_excess excess_searches = Search.where(employee_id: employee_id).where(type: self.class.name).where('persist IS NULL or NOT persist').order(id: :desc).offset(self.class.maximum_unpersisted_queries) excess_searches.destroy_all end |
#effective_name ⇒ Object
383 384 385 |
# File 'app/models/search.rb', line 383 def effective_name name.presence || human_query_params.presence || 'Unfiltered Search' end |
#effective_short_name ⇒ Object
def create_custom_column(results, column_name, type)
pgc = ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(column_name, nil, type)
results.columns << pgc unless results.columns.include?(pgc)
results.column_names << column_name unless results.column_names.include?(column_name)
end
379 380 381 |
# File 'app/models/search.rb', line 379 def effective_short_name name || "#{self.class.name.humanize.titleize} #{created_at.to_fs(:crm_default)}" end |
#employee ⇒ Employee
62 |
# File 'app/models/search.rb', line 62 belongs_to :employee, inverse_of: :searches, optional: true |
#enqueue_navbar_pinned_refresh ⇒ Object
Schedules a single coalesced Turbo Stream replace of this employee's
pinned-items navbar badge. Safe to call from any code path that may have
changed the pinned set — bulk SQL helpers, AR callbacks, controllers.
777 778 779 780 781 |
# File 'app/models/search.rb', line 777 def return if employee_id.blank? CrmNavbarRefreshWorker.schedule(user_id: employee_id, badge: :pinned) end |
#fast_count ⇒ Object
408 409 410 411 412 |
# File 'app/models/search.rb', line 408 def fast_count results = apply_criteria results.select(selected_columns.first) results.count end |
#get_pinned_results ⇒ Object
398 399 400 |
# File 'app/models/search.rb', line 398 def get_pinned_results pinned_query.includes(:resource).order(:position) # ensures we only show resource that still exists end |
#get_sort_term(column_name, sort_direction = 'ASC') ⇒ Object
Generate the SQL sort term for a given column,
it checks if the column name is a composite column with a custom select, in which
case replace the sort term
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 |
# File 'app/models/search.rb', line 588 def get_sort_term(column_name, sort_direction = 'ASC') sort_terms = [] if (composite_attrs = all_composite_columns[column_name.to_sym]) if composite_attrs[:order] sort_terms << composite_attrs[:order] elsif composite_attrs[:select] sort_terms = composite_attrs[:select] else sort_terms << column_name end else sort_terms << if self.class.view_resource_table == column_name.split('.')[0] column_name.to_s else "#{self.class.view_resource_table}.#{column_name}" end end sort_terms.map { |term| "#{term} #{sort_direction}" } end |
#human_query_params ⇒ Object
387 388 389 390 391 |
# File 'app/models/search.rb', line 387 def human_query_params query_params.filter_map { |k, v| v.present? ? "#{k.humanize}: #{v}" : nil }.join(' and ') rescue StandardError nil end |
#instantiate_resource_updater(params = {}) ⇒ Object (protected)
950 951 952 |
# File 'app/models/search.rb', line 950 def instantiate_resource_updater(params = {}) ResourceUpdater.new(self, params) end |
#limit_options ⇒ Object
if you want to override the available result set size you can do so in your specialized class.
394 395 396 |
# File 'app/models/search.rb', line 394 def [10, 25, 50, 100, 200, 500, 1000, 5000] end |
#mass_export(params = {}, cur_user = nil, path = nil) ⇒ Object
Mass Export
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 |
# File 'app/models/search.rb', line 788 def mass_export(params = {}, cur_user = nil, path = nil) params ||= {} file_format = params[:file_format].presence || 'CSV' file_extension = file_format.downcase file_name = "export_#{id}.#{file_extension}" directory_path = path || Rails.root.join(Rails.application.config.x.temp_storage_path.to_s, 'users', cur_user ? cur_user.id.to_s : '') FileUtils.mkdir_p(directory_path) file_path = Rails.root.join(directory_path, file_name) # Prepare export data results = perform(nil, nil, nil, false, 100_000, false) .where("exists(select id from search_results sr where sr.search_id = ? and sr.resource_id = #{self.class.view_resource_table}.id)", id) search_composite_map = Search.find(id).type.constantize.composite_columns.to_h validated_composite_columns = validated_selected_columns.map do |column_name| [column_name, search_composite_map[column_name]&.dig(:select, 0) || column_name] end include_headers = params[:include_headers].to_b headers = validated_selected_columns.map { |column_name| self.class.friendly_column_name(column_name) } if file_format == 'XLSX' export_xlsx(file_path, results, validated_composite_columns, headers, include_headers, params) else export_csv(file_path, results, validated_composite_columns, headers, include_headers, params) end { status: :ok, result_file: file_name, result_file_path: file_path } end |
#normalize_for_mass_update(_resource, _params) ⇒ Object
Per-record hook called by SearchResourceUpdateWorker before saving each resource.
Override in subclasses to apply custom normalization or guard logic.
Return false to skip the save and count the record as an error.
Must be public — the worker invokes it from another class (not a valid protected call).
908 909 910 |
# File 'app/models/search.rb', line 908 def normalize_for_mass_update(_resource, _params) true end |
#output_columns_for_select ⇒ Object
Renders a select list of possible output columns including all
composite columns, note that we only include class level, permanent composite
columns and not instance level which can disapear
315 316 317 318 319 |
# File 'app/models/search.rb', line 315 def output_columns_for_select # Start with the ones already selected columns = validated_selected_columns | self.class.available_output_columns.sort_by { |cn| self.class.friendly_column_name(cn) } columns.map { |cn| [self.class.friendly_column_name(cn), cn] } end |
#pagy_from_search ⇒ Object
Backward-compatible reader: returns a simple struct with count/page/limit so existing
controller code (pagy(:offset, results, count:, page:, limit:)) keeps working unchanged.
54 55 56 57 58 |
# File 'app/models/search.rb', line 54 def pagy_from_search return nil unless @pagy_count Data.define(:count, :page, :limit).new(count: @pagy_count, page: @pagy_page, limit: @pagy_limit) end |
#perform(page = 1, sort = nil, direction = nil, record_results = false, per_page = nil, autosave = true, disable_pagination = false) ⇒ Object
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 |
# File 'app/models/search.rb', line 414 def perform(page = 1, sort = nil, direction = nil, record_results = false, per_page = nil, autosave = true, disable_pagination = false) results = apply_criteria # Count BEFORE applying complex SELECT to avoid SQL syntax errors total_count = results.count if !disable_pagination && results.respond_to?(:limit) results = apply_select(results) set_sort_term(sort, direction) results = apply_sort(results) if !disable_pagination && results.respond_to?(:limit) @pagy_page = (page || 1).to_i @pagy_limit = (per_page || set_limit).to_i @pagy_count = total_count results = results.limit(@pagy_limit).offset((@pagy_page - 1) * @pagy_limit) self.result_set_size = @pagy_count else @pagy_count = @pagy_page = @pagy_limit = nil self.result_set_size = results.size end if autosave save if record_results search_results.delete_all results.find_each do |r| search_results.create(resource_type: (begin r.class.main_resource_class rescue StandardError nil end) || r.class.to_s, resource_id: r.id) end end results.to_a.compact.each do |r| r.class.send(:attr_accessor, :search_selected) r.search_selected = if r.respond_to? :selected_attr r.selected_attr else search_results.exists?(resource_type: r.class.name, resource_id: r.id) end end end results end |
#perform_selected(selected_columns: nil) ⇒ Object
459 460 461 462 |
# File 'app/models/search.rb', line 459 def perform_selected(selected_columns: nil) self.selected_columns = selected_columns if selected_columns.present? perform(nil, nil, nil, false, nil, false, true).where("exists(select id from search_results sr where sr.search_id = ? and sr.resource_id = #{self.class.view_resource_table}.id)", id) end |
#pinned_query ⇒ Object
Retrieves the pinned search results, in most case this is suffucient but for speed purpose you might want to override and specialize
e.g. if you pin presenter displays information in associated model you might want to include the associated table
404 405 406 |
# File 'app/models/search.rb', line 404 def pinned_query search_results end |
#prepend_custom_column(*options) ⇒ void
This method returns an undefined value.
Same as append_custom_column except it adds it to the front of the column list.
358 359 360 361 362 |
# File 'app/models/search.rb', line 358 def prepend_custom_column(*) column_name = .first.to_s composite_column(*) prepend_output_column(column_name) end |
#prepend_output_column(column_name) ⇒ Object
369 370 371 372 |
# File 'app/models/search.rb', line 369 def prepend_output_column(column_name) selected_columns.delete(column_name) # delete it if present already selected_columns.insert(0, column_name) end |
#ransack_probe_params ⇒ Object
Returns the query_params hash with non-Ransack keys stripped for the strict probe.
Subclasses can override to remove additional dynamic keys (e.g. spec_field_* in ItemSearch).
497 498 499 |
# File 'app/models/search.rb', line 497 def ransack_probe_params query_params.except(*DISTANCE_SEARCH_KEYS, *self.class.custom_criteria_keys) end |
#record_list(resource_ids) ⇒ Object
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 |
# File 'app/models/search.rb', line 701 def record_list(resource_ids) if resource_ids == '*' results = apply_criteria results = results.except(:select).select "'#{self.class.main_resource_class}', #{self.class.view_resource_table}.id, now(), now(), #{id}" # By far the fastest method is straight sql first to wipe all existing SearchResult.delete_by(search_id: id) # Bulk magic sql = %{ INSERT INTO search_results ( resource_type, resource_id, created_at, updated_at, search_id ) #{results.to_sql} ON CONFLICT DO NOTHING } ActiveRecord::Base.lease_connection.execute(sql) else return if resource_ids.blank? search_results = resource_ids.map do |raw_resource_id| resource_type, resource_id = raw_resource_id.split('|') { resource_type: resource_type, resource_id: resource_id, search_id: id } end SearchResult.insert_all(search_results) if search_results.any? end if pinned true end |
#refresh_pinned_results ⇒ Object
Re-runs the saved search criteria as a prune over the user's curated
pin selection: keeps the intersection of (currently-pinned IDs) and
(records that still match the criteria), drops the rest. Never adds new
rows — a user who pinned 5 specific records gets at most 5 back, with
stale ones (e.g. an opportunity that was closed) removed.
Returns the number of stale rows removed.
760 761 762 763 764 765 766 767 768 769 770 771 772 |
# File 'app/models/search.rb', line 760 def refresh_pinned_results main_type = self.class.main_resource_class pinned_ids = search_results.where(resource_type: main_type).pluck(:resource_id) return 0 if pinned_ids.empty? matching_ids = apply_criteria.except(:select).where(id: pinned_ids).ids stale_ids = pinned_ids - matching_ids return 0 if stale_ids.empty? search_results.delete_by(resource_type: main_type, resource_id: stale_ids) if pinned stale_ids.size end |
#remove_other_pins ⇒ Object (protected)
966 967 968 969 970 |
# File 'app/models/search.rb', line 966 def remove_other_pins return unless employee && pinned_changed? && pinned employee.searches.where(pinned: true).update_all(pinned: false) end |
#search_name ⇒ Object
783 784 785 |
# File 'app/models/search.rb', line 783 def search_name name || self.class.search_name end |
#search_results ⇒ ActiveRecord::Relation<SearchResult>
63 |
# File 'app/models/search.rb', line 63 has_many :search_results, inverse_of: :search, dependent: :delete_all |
#select_sort_columns ⇒ Object
Instance level list of possible sort columns
322 323 324 325 326 |
# File 'app/models/search.rb', line 322 def select_sort_columns # Current sort columns current_sort_columns = sort_columns.map { |sc| sc.split&.first&.to_sym } available_output_columns | current_sort_columns end |
#select_statement ⇒ Object (protected)
958 959 960 |
# File 'app/models/search.rb', line 958 def select_statement @sql_select_columns.join(',') end |
#set_defaults ⇒ Object
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 |
# File 'app/models/search.rb', line 878 def set_defaults self.set_limit = 100 if set_limit.blank? self.persist = false if persist.blank? self.name = nil if name.blank? self.selected_columns = (self.class.default_selected_columns.presence || available_output_columns) if selected_columns.nil? || selected_columns.blank? # Apply default sort columns if sort_columns.blank? if self.class.default_sort_columns.present? self.sort_columns = (self.class.default_sort_columns & available_output_columns) elsif available_output_columns.include?(:created_at) # self.sort_columns = ["#{self.class.view_resource_table}.created_at desc"] self.sort_columns = ['created_at DESC'] end end self.sort_columns ||= [] end |
#set_sort_term(set_sort_column, set_sort_direction = nil) ⇒ Object
Given a single sort column applies it and overrides any previous search ordering
609 610 611 612 613 614 615 616 617 618 619 |
# File 'app/models/search.rb', line 609 def set_sort_term(set_sort_column, set_sort_direction = nil) return if set_sort_column.blank? set_sort_direction ||= 'ASC' self.sort_columns = if available_output_columns.include? set_sort_column.to_sym ["#{set_sort_column.downcase} #{set_sort_direction.downcase}"] else [] end end |
#sort_column1 ⇒ Object
BEGIN INSTANCE VARIABLES #
248 249 250 |
# File 'app/models/search.rb', line 248 def sort_column1 sort_columns.try(:[], 0) end |
#sort_column1=(val) ⇒ Object
252 253 254 255 256 257 258 |
# File 'app/models/search.rb', line 252 def sort_column1=(val) if val.present? self.sort_columns = sort_columns.insert(0, val).compact.uniq else sort_columns.delete_at(0) end end |
#sort_column2 ⇒ Object
260 261 262 |
# File 'app/models/search.rb', line 260 def sort_column2 sort_columns.try(:[], 1) end |
#sort_column2=(val) ⇒ Object
264 265 266 267 268 269 270 |
# File 'app/models/search.rb', line 264 def sort_column2=(val) if val.present? self.sort_columns = sort_columns.insert(1, val).compact.uniq else sort_columns.delete_at(1) end end |
#sort_column3 ⇒ Object
272 273 274 |
# File 'app/models/search.rb', line 272 def sort_column3 sort_columns.try(:[], 2) end |
#sort_column3=(val) ⇒ Object
276 277 278 279 280 281 282 |
# File 'app/models/search.rb', line 276 def sort_column3=(val) if val.present? self.sort_columns = sort_columns.insert(2, val).compact.uniq else sort_columns.delete_at(2) end end |
#sort_columns_for_select ⇒ Object
Retrieves a list of sort columns suitable for a select
635 636 637 638 639 640 641 642 643 644 |
# File 'app/models/search.rb', line 635 def sort_columns_for_select selection_list = [] select_sort_columns.each do |sc| selection_list << ["#{sc.to_s.humanize} ascending", "#{sc} ASC"] selection_list << ["#{sc.to_s.humanize} descending", "#{sc} DESC"] end # Add composite keys selection_list end |
#sort_columns_to_hash ⇒ Object
Takes all the sort columns specified for a search and returns them as a
hash with a symbolized key being the column name and the value being the sort
direction
e.g. { :primary_sales_rep_name => :DESC, :amount => :ASC }
625 626 627 628 629 630 631 632 |
# File 'app/models/search.rb', line 625 def sort_columns_to_hash return {} if sort_columns.blank? sort_columns.each_with_object({}) do |sort_combined, hsh| sort_column, sort_direction = sort_combined.split hsh[sort_column.to_sym] = (sort_direction || :ASC).to_sym end end |
#sortable_column?(column_name) ⇒ Boolean
Whether +column_name+ can appear in a SQL ORDER BY clause. Real database
columns always can. Composite columns can only when they map to concrete
SQL — an explicit :order expression, a :select of real columns, or a :raw
expression (which becomes a SELECT alias the ORDER BY can reference, e.g.
the :distance column added by #apply_distance_search).
"Virtual" composite columns that are assembled by the presenter from
associations (e.g. SupportCaseSearch's :participants / :rooms) have none of
these; ordering by them emits a bare, non-existent identifier and raises
PG::UndefinedColumn (AppSignal #6124), so they are not sortable.
655 656 657 658 659 660 |
# File 'app/models/search.rb', line 655 def sortable_column?(column_name) composite_attrs = all_composite_columns[column_name.to_sym] return true if composite_attrs.nil? composite_attrs[:order].present? || composite_attrs[:select].present? || composite_attrs[:raw].present? end |
#target_geocoding ⇒ Object (protected)
914 915 916 917 918 919 920 921 922 923 924 925 926 |
# File 'app/models/search.rb', line 914 def target_geocoding return unless query_params if query_params[:within_miles_of].present? if query_params[:target_coordinates].blank? || (query_params_was[:within_miles_of] != query_params[:within_miles_of]) logger.info "Attempt geocoding #{query_params[:within_miles_of]}" query_params[:target_coordinates] = Geocoder.coordinates(query_params[:within_miles_of]) errors.add(:query_params, "error, cannot geocode #{query_params[:within_miles_of]}") unless query_params[:target_coordinates] end else query_params.delete(:target_coordinates) end end |
#unrecord_list(resource_ids) ⇒ Object
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 |
# File 'app/models/search.rb', line 732 def unrecord_list(resource_ids) return if resource_ids.blank? if resource_ids == '*' SearchResult.delete_by(search_id: id) else SearchResult.transaction do # One transaction wrap makes this faster hsh_delete = {} resource_ids.each do |raw_resource_id| resource_type, resource_id = raw_resource_id.split('|') hsh_delete[resource_type] ||= [] hsh_delete[resource_type] << resource_id end hsh_delete.each do |(resource_type, resource_ids)| SearchResult.delete_by(search_id: id, resource_type: resource_type, resource_id: resource_ids) end end end if pinned end |
#validated_selected_columns ⇒ Object
Presentation layer on top of store array attribute selected columns,
makes sure to symbolize keys, prevents duplicate, cleans up blanks.
332 333 334 |
# File 'app/models/search.rb', line 332 def validated_selected_columns (selected_columns || []).map { |c| c.presence&.to_sym }.uniq.compact end |
#view_resource_klass ⇒ Object
Alias for Class#view_resource_klass
328 |
# File 'app/models/search.rb', line 328 delegate :view_resource_klass, to: :class |