Class: SearchesController

Inherits:
CrmController show all
Defined in:
app/controllers/searches_controller.rb

Constant Summary

Constants included from Controllers::ReferenceFindable

Controllers::ReferenceFindable::ID_EMBEDDED_PATTERNS

Constants included from Controllers::AnalyticsEvents

Controllers::AnalyticsEvents::MAX_QUEUED_EVENTS, Controllers::AnalyticsEvents::SESSION_KEY

Constants included from Controllers::ErrorRendering

Controllers::ErrorRendering::NON_CONTENT_PATH_PREFIXES

Constants included from Www::SeoHelper

Www::SeoHelper::AWARDS, Www::SeoHelper::CA_ADDRESS, Www::SeoHelper::CA_BUSINESS_HOURS, Www::SeoHelper::CA_CONTACT_POINT, Www::SeoHelper::CA_CURRENCIES, Www::SeoHelper::CA_DESCRIPTION, Www::SeoHelper::CA_FOUNDING_DATE, Www::SeoHelper::CA_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::CA_LEGAL_NAME, Www::SeoHelper::CA_LOCAL_BUSINESS, Www::SeoHelper::CA_ONLINE_STORE, Www::SeoHelper::CA_RETURN_POLICY, Www::SeoHelper::CA_SALES_DEPARTMENT, Www::SeoHelper::CA_SERVICE_AREA, Www::SeoHelper::CA_URL, Www::SeoHelper::CA_VAT_ID, Www::SeoHelper::CA_WAREHOUSE_DEPARTMENT, Www::SeoHelper::CA_WAREHOUSE_HOURS, Www::SeoHelper::COMPANY_EMAIL, Www::SeoHelper::COMPANY_LOGO, Www::SeoHelper::COMPANY_NAME, Www::SeoHelper::COMPANY_SLOGAN, Www::SeoHelper::EXPERTISE, Www::SeoHelper::FAX_NUMBER, Www::SeoHelper::GS1_COMPANY_PREFIX, Www::SeoHelper::ISO6523_CODE, Www::SeoHelper::PAYMENT_METHODS, Www::SeoHelper::PHONE_NUMBER, Www::SeoHelper::PRIMARY_NAICS, Www::SeoHelper::REFUND_TYPE, Www::SeoHelper::RETURN_FEES, Www::SeoHelper::RETURN_METHOD, Www::SeoHelper::RETURN_POLICY_CATEGORY, Www::SeoHelper::SECONDARY_NAICS, Www::SeoHelper::SOCIAL_PROFILES, Www::SeoHelper::US_ADDRESS, Www::SeoHelper::US_BUSINESS_HOURS, Www::SeoHelper::US_CONTACT_POINT, Www::SeoHelper::US_CURRENCIES, Www::SeoHelper::US_DESCRIPTION, Www::SeoHelper::US_FOUNDING_DATE, Www::SeoHelper::US_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::US_IMAGE, Www::SeoHelper::US_LEGAL_NAME, Www::SeoHelper::US_LOCAL_BUSINESS, Www::SeoHelper::US_ONLINE_STORE, Www::SeoHelper::US_RETURN_POLICY, Www::SeoHelper::US_SALES_DEPARTMENT, Www::SeoHelper::US_SERVICE_AREA, Www::SeoHelper::US_TAX_ID, Www::SeoHelper::US_URL, Www::SeoHelper::US_WAREHOUSE_DEPARTMENT, Www::SeoHelper::US_WAREHOUSE_HOURS

Constants included from IconHelper

IconHelper::CUSTOM_ICON_MAP, IconHelper::CUSTOM_SVG_DIR, IconHelper::DEFAULT_FAMILY

Instance Method Summary collapse

Methods inherited from CrmController

#access_denied, #context_id, #context_object, #crm_home_path, #current_ability, #default_url_options, #download_temp, #get_tempfile_path_for_download, #initialize_crm_lazy_chunks, #record_not_found, #redirect_to_job_or_fallback, #render_edit_action, #set_context, #set_download_path, #stash_file_for_temp_download

Methods inherited from ApplicationController

#account_impersonated?, #add_to_flash, #append_token, #bypass_forgery_protection?, #chat_enabled?, #cloudflare_cleared?, #default_catalog, #default_url_options, #enable_turbo_frames, #find_publication, #fix_invalid_accept_header, #init_js_utils, #is_globals_call?, #layout_by_resource, #locale_store, #redirect_to, #require_employee_for_crm, #set_base_host, #set_real_ip, #set_report_errors_for, #should_render_layout?, #stamp_impersonation_context, #warmlyyours_canada_ip?, #warmlyyours_ip?, #y

Methods included from Controllers::ReturnPathHandling

#check_for_return_path, #redirect_to_return_path_or_default

Methods included from Controllers::AnalyticsEvents

#consume_queued_analytics_events, #track_event

Methods included from Controllers::DeviceDetection

#device_detector, #is_ie?

Methods included from Controllers::SubdomainDetection

#is_crm_request?, #is_www_request?, #json_request?

Methods included from Controllers::TrackingDetection

#bot_request?, #gdpr_country?, #gdpr_country_data, #prevent_bots, #set_tracking_cookie, #track_visitor?

Methods included from Controllers::AcceleratedFileSending

#send_file_accelerated, #send_upload_accelerated

Methods included from Controllers::ErrorRendering

#excp_string, #mail_to_for_error_reporting, #render_400, #render_404, #render_406, #render_410, #render_500, #render_invalid_authenticity_token, #render_ip_spoof_error, #safe_referer_or_fallback

Methods included from Controllers::TurnstileVerification

#load_turnstile_script_tag, #turnstile_lazy_widget, #turnstile_script_tag, #turnstile_widget, #validate_turnstile!

Methods included from Controllers::CloudflareCaching

edge_cached, #edge_cached_action?, #reset_cloudflare_cache, #set_cloudflare_cache, #skip_session

Methods included from Controllers::Webpackable

#preload_webpack_fonts, #webpack_css_include, #webpack_css_url, #webpack_js_include, #wpd_is_running?

Methods included from Controllers::Localizable

#cloudflare_country_locale, #determine_request_locale, #geocoder_locale, #guest_user_locale_check, #locale_optional_www_auth_path?, #param_locale, #set_locale, #set_request_locale, #skip_localization?, #warmlyyours_ip_locale

Methods included from Controllers::Authenticable

#access_denied, #authenticate_account, #authenticate_account!, #authenticate_account_from_login_token!, #authenticate_account_from_token!, #check_is_a_manager, #check_is_a_sales_manager, #check_is_an_admin, #check_is_an_employee, #check_party, #clear_mismatched_guest_user, #create_guest_user, #credentials?, #current_or_guest_user, #current_or_guest_user_id_read_only, #current_user, #devise_mapping, #fully_logged_in?, #generate_bot_id, #guest_user, #identifiable?, #init_current_user, #initialize_guest, #load_context_user, #logging_in, #resource, #resource_name, #restrict_access_for_non_employees, #scrubbed_request_path, #user_object, #warn_on_session_guest_id_leak

Methods included from ApplicationHelper

#better_number_to_currency, #check_force_logout, #check_or_cross, #check_or_times, #error_messages, #general_disclaimer_on_product_installation_and_local_codes, #gridjs_from_html_table, #gridjs_table, #is_wy_ip, #line_break, #parent_layout, #pass_or_fail, #render_error_messages_list, #render_video_card, #resolved_auth_form_turbo_frame, #return_path_or, #safe_css_color, #set_return_path_if_present, #set_section_if_present, #tab_frame_id, #to_underscore, #track_page?, #turbo_section_wrapper, #turbo_tabs_request?, #url_on_same_domain_as_request, #widget_index_daily_focus_index_path, #working_hours?, #yes_or_no, #yes_or_no_highlighted, #yes_or_no_with_check_or_cross, #youtube_video

Methods included from UppyUploaderHelper

#file_uploader, #image_uploader, #large_file_uploader_s3, #lead_sketch_uploader, #rma_image_uploader, #rma_image_uploader_s3, #uppy_uploader, #video_uploader

Methods included from Www::ImagesHelper

#image_asset_tag, #image_asset_url

Methods included from Www::SeoHelper

#add_page_schema, #canada?, #company_social_links, #ensure_context_json, #json_ld_script_tag, #local_business_schema, #online_store_id, #online_store_schema, #page_main_entity, #page_main_entity_json, #render_auto_collection_page_schema, #render_collection_page_schema, #render_local_business_schema, #render_online_store_schema, #render_page_schemas, #render_page_video_schemas, #render_webpage_schema, #render_webpage_schema_with_collections, #usa?

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Methods included from IconHelper

#account_nav_icon, #fa_icon, #star_rating_html

Instance Method Details

#advanced_search_panelObject

Lazy-loaded body for the navbar's Advanced Search offcanvas. The navbar
renders an empty <turbo-frame id="advancedSearchOffcanvasFrame"> shell;
crm-navbar Stimulus sets the frame's src to this action on every
show.bs.offcanvas, so the per-type recent-search and template queries
only run when the user actually opens the panel.

expires_in 15.seconds, public: false lets the user's browser cache
the response for rapid re-opens (matches the lazy navbar dropdown
bodies) while keeping responses out of any shared cache. Per-user
data, so private is mandatory.



34
35
36
37
# File 'app/controllers/searches_controller.rb', line 34

def advanced_search_panel
  expires_in 15.seconds, public: false
  render partial: 'advanced_search_offcanvas_body', layout: false
end

#createObject



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'app/controllers/searches_controller.rb', line 87

def create
  template_key = params[:template_key] || :default
  p = search_class.try(:global_favorites).try(:dig, template_key) || {}
  p = p.slice(:query_params, :sort_columns)
  p = p.deep_merge(params[:search].to_h || {})
  @search = search_class.new(p)

  @search.employee_id = employee.id
  authorize! :create, @search
  if @search.save
    if @search.pinned
      @search.perform(1, nil, nil, true)
      redir_url = get_first_search_result_link
      if redir_url
        flash[:info] = 'Search results were automatically pinned'
        redirect_to redir_url
      else
        redirect_to search_path(@search)
      end
    elsif params[:edit].present?
      redirect_to edit_search_path(@search)
    else
      redirect_to search_path(@search)
    end
  else
    render :new, status: :unprocessable_entity
  end
end

#create_onboarding_packetObject



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'app/controllers/searches_controller.rb', line 313

def create_onboarding_packet
  require 'zip'
  @search = Search.find(params[:id])
  zip_name = "onboarding_#{@search.id}.zip"
  directory_path = Rails.root.join(Rails.application.config.x.temp_storage_path.to_s, 'users', current_user ? current_user.id.to_s : '')
  FileUtils.mkdir_p(directory_path)
  zip_path = Rails.root.join(directory_path, zip_name)

  Zip::File.open(zip_path, create: true) do |zipfile|
    file_name = "onboarding_#{@search.id}#{"_#{zipfile.size}" if zipfile.size.positive?}.csv"
    file_path = Rails.root.join(directory_path, file_name)
    CSV.open(file_path, 'w', force_quotes: true, encoding: 'WINDOWS-1252') do |csv|
      csv << ['item_name', 'item_sku', 'product_category', 'brand_name', 'collection', 'UPC', 'product_weight',
              'product_height', 'product_width', 'product_depth', 'shipping_weight', 'shipping_height', 'shipping_width',
              'shipping_depth', 'shipping_type', 'product_description', 'bullet_point_1', 'bullet_point_2', 'bullet_point_3', 'bullet_point_4',
              'bullet_point_5', 'country_of_origin', 'Length', 'Amps', 'Weight', 'Width', 'Voltage', 'Heating Wire', 'Floor Types',
              'Approvals', 'Watts', 'BTU per Hour', 'Heated Area']

      @search.search_results.each do |result|
        row = []
        catalog_item = result.resource
        item = catalog_item.item
        row.push(item.name, item.sku, item.brand_id.equal?(1) ? 'WarmlyYours USA' : 'WarmlyYours CA', 'collection',
                 item.upc, item.base_weight, 'product_height', item.width, 'product_depth', item.shipping_weight, item.shipping_height,
                 item.shipping_width, 'shipping_depth', 'shipping_type',
                 item.detailed_description.presence || catalog_item.primary_product_line.description,
                 'bullet_point_1', 'bullet_point_2', 'bullet_point_3', 'bullet_point_4', 'bullet_point_5', item.coo)
        item.product_specifications.each do |spec|
          row << spec.get_specification_data(catalog_item).formatted
        end
        csv << row
      end
    end
    zipfile.add(file_name, file_path)
  end

  session[:download_path] = "/tempfile/#{zip_name}"
  flash[:info] = "File created <br><a href='#{session[:download_path]}' class='btn btn-outline-primary'>Download</a>"
  redirect_back_or_to(root_path)
end

#destroyObject



370
371
372
373
374
# File 'app/controllers/searches_controller.rb', line 370

def destroy
  @search = @context_user.searches.find(params[:id])
  flash[:info] = "Search id #{@search.id} deleted." if @search.destroy
  redirect_to_return_path_or_default employee_searches_path(@context_user, type: @search.class.name)
end

#editObject



83
84
85
# File 'app/controllers/searches_controller.rb', line 83

def edit
  @search = Search.find(params[:id])
end


203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'app/controllers/searches_controller.rb', line 203

def get_first_search_result_link
  return unless (first_resource = @search.search_results.first&.resource)

  begin
    first_resource.crm_link
  rescue StandardError
    nil
  end || begin
    polymorphic_path(first_resource)
  rescue StandardError
    nil
  end
end

#indexObject



7
8
9
10
11
12
13
14
# File 'app/controllers/searches_controller.rb', line 7

def index
  if params[:type].present?
    search_class # sets the search class
    @searches = employee.searches.where(type: params[:type]).order('created_at DESC')
  else
    redirect_to(search_types_searches_path)
  end
end

#mass_actionObject



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'app/controllers/searches_controller.rb', line 276

def mass_action
  expires_now
  @search = Search.find(params[:id])
  @mass_action_type = params[:mass_action_type]
  if @search.search_results.empty?
    redirect_to search_path(@search), flash: { error: 'You must select some records before performing a mass action.' }
  elsif @mass_action_type.blank? || !@search.respond_to?(@mass_action_type)
    redirect_to search_path(@search), flash: { error: 'You must select a valid mass action to perform.' }
  else
    # Try to find if a partial is present to render a pre-execution form to capture params or an action to perform directly
    @mass_action = OpenStruct.new(params[:mass_action].to_h)
    view_root_path = Rails.root.join('app/views/searches')
    # Is there a search model specific view for this mass action?
    if File.exist?(view_root_path.join(@search.class.name.underscore, "_#{@mass_action_type}.html.erb"))
      @mass_action_partial_path = "/searches/#{@search.class.name.underscore}/#{@mass_action_type}"
    # Is there a generalized search view?
    elsif File.exist?(view_root_path.join("_#{@mass_action_type}.html.erb"))
      @mass_action_partial_path = "/searches/#{@mass_action_type}"
    # execute mass action directly
    else
      @search.send(@mass_action_type.to_sym, self)
    end
  end
end

#newObject



70
71
72
73
74
75
76
77
78
79
80
81
# File 'app/controllers/searches_controller.rb', line 70

def new
  template_key = params[:template_key] || :default
  p = search_class.try(:global_favorites).try(:dig, template_key) || {}
  p = p.slice(:query_params, :sort_columns)
  p = p.deep_merge(params[:search] || {})
  p[:set_limit] ||= 50
  p[:employee] ||= employee
  p[:query_params] = params[:query_params]&.to_h&.presence || p[:query_params] || {}
  @search = search_class.new(p)
  @search.set_defaults
  authorize! :create, @search
end

#onboarding_packetObject



301
302
303
304
305
306
307
308
309
310
311
# File 'app/controllers/searches_controller.rb', line 301

def onboarding_packet
  @search = Search.find(params[:id])
  # @search.search_results.each do |result|
  #   catalog_item = result.resource
  #   item = catalog_item.item
  #
  # end
  respond_to do |format|
    format.html
  end
end

#perform_mass_actionObject



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'app/controllers/searches_controller.rb', line 354

def perform_mass_action
  mass_action_params = (params[:mass_action].to_h || {}).deep_squish.with_indifferent_access
  @search      = Search.find(params[:id])
  @mass_action = params[:mass_action_type]

  unless @mass_action.present? && @search.respond_to?(@mass_action)
    flash[:error] = "Mass action #{params[:mass_action]} not recognized"
    redirect_to search_path(@search) and return
  end

  # Every mass action method now owns its own background execution by enqueuing
  # a worker and returning { job_id: }. The controller always calls the method
  # directly and routes based on the response type.
  run_mass_action_synchronously(mass_action_params)
end

#pinObject



224
225
226
227
228
229
230
231
232
# File 'app/controllers/searches_controller.rb', line 224

def pin
  @search = Search.find(params[:id])
  if @search.search_results.present?
    mark_pinned_and_redirect
  else
    flash[:error] = 'Your search has no results that can be pinned.  Select records before attempting pinning.'
    redirect_to search_path(@search)
  end
end

#pin_allObject



217
218
219
220
221
222
# File 'app/controllers/searches_controller.rb', line 217

def pin_all
  @search = Search.find(params[:id])
  @search.record_list('*')
  @search.update_attribute(:pinned, true)
  mark_pinned_and_redirect
end

#pinned_resultsObject



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'app/controllers/searches_controller.rb', line 234

def pinned_results
  results = {}
  @search = Search.where(employee_id: params[:employee_id]).where(pinned: true).first
  @context_type = params[:context_type]
  @context_id = params[:context_id]
  @current_path = params[:current_path]
  if @search
    @pagy, @results = pagy(@search.get_pinned_results, limit: 20)
    results[:status] = :ok
    results[:results] = @results
  else
    results[:status] = :not_found
  end

  respond_to do |format|
    format.turbo_stream
    format.json { render json: results }
    format.html { render layout: false }
  end
end

#recordObject



131
132
133
134
135
136
137
138
139
140
141
142
# File 'app/controllers/searches_controller.rb', line 131

def record
  @search = Search.find(params[:id])
  check_list_ids = params[:check_list_ids]
  uncheck_list_ids = params[:uncheck_list_ids]

  @search.record_list(check_list_ids)
  @search.unrecord_list(uncheck_list_ids)

  respond_to do |format|
    format.json { render json: { status: :ok, selected: @search.search_results.count, checked: check_list_ids, unchecked: uncheck_list_ids } }
  end
end

#refresh_pinnedObject

Prunes the user's pinned set against current data: keeps only the records
that still match the saved criteria (e.g. drops an opportunity that was
closed and no longer satisfies an "open" filter). Never auto-adds new
rows — pinning is a curated subset and we respect that. Mutating action —
POST only, and restricted to the current user's own pinned search.



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'app/controllers/searches_controller.rb', line 260

def refresh_pinned
  head :forbidden and return unless current_user.id.to_s == params[:employee_id].to_s

  @search = Search.where(employee_id: current_user.id, pinned: true).first
  @current_path = params[:current_path]

  if @search
    @search.refresh_pinned_results
    @pagy, @results = pagy(@search.get_pinned_results, limit: 20)
  end

  respond_to do |format|
    format.turbo_stream { render :pinned_results }
  end
end

#resetObject



116
117
118
119
120
121
# File 'app/controllers/searches_controller.rb', line 116

def reset
  @search = Search.find(params[:id])
  @search.query_params = nil
  @search.save
  redirect_to search_path(@search)
end

#search_and_pinObject

Executes a search and pin in one shot



190
191
192
193
194
195
196
197
198
199
200
201
# File 'app/controllers/searches_controller.rb', line 190

def search_and_pin
  @search = params[:id].present? ? Search.find(params[:id]) : search_class.new(params[:search])
  @search.employee_id = employee.id
  @search.pinned = true
  if @search.save
    @search.perform(1, nil, nil, true) # True stores the pinned search
    @results = @search.pinned_query
  end
  respond_to do |format|
    format.turbo_stream
  end
end

#search_and_showObject



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'app/controllers/searches_controller.rb', line 153

def search_and_show
  @search = search_class.new(set_limit: params[:set_limit] || 50, employee: @context_user, query_params: params[:query_params].to_h.presence || {},
                             selected_columns: params[:selected_columns], sort_columns: params[:sort_columns])
  @links = params[:links] || {}
  @target_id = params[:target_id] || 'search_and_show'
  action_view = params[:search_mode] == 'full' ? 'show' : 'search_and_show'
  unless @search.save
    # Search criteria failed validation: surface the error rather than
    # returning an empty 200 (which Turbo silently discards).
    flash.now[:error] = "Search could not be saved: #{@search.errors_to_s}"
    respond_to do |format|
      format.html { render plain: flash.now[:error], status: :unprocessable_entity }
      format.turbo_stream { render partial: 'shared/flash', status: :unprocessable_entity }
    end
    return
  end

  # Perform paginated query; Search will expose Pagy via pagy_from_search
  page = (params[:page] || 1).to_i
  # Pagy uses params[:items] for per-page count, fall back to saved set_limit
  per_page = params[:items].presence || @search.set_limit
  @results = @search.perform(page, params[:sort], params[:direction], false, per_page, true, false)
  # Create Pagy object with request context for URL generation
  if @search.pagy_from_search
    @pagy = pagy(:offset, @results,
                 count: @search.pagy_from_search.count,
                 page: @search.pagy_from_search.page,
                 limit: @search.pagy_from_search.limit)[0]
  end

  respond_to do |format|
    format.html { render action: action_view, layout: should_render_layout? }
    format.turbo_stream
  end
end

#search_typesObject



16
17
18
19
20
21
22
# File 'app/controllers/searches_controller.rb', line 16

def search_types
  @search_types = Search.options
  # `should_render_layout?` keeps the response usable both as a real page
  # (e.g. when SearchesController#index redirects here with no `:type`)
  # and as a turbo-frame fragment.
  render layout: should_render_layout?
end

#showObject



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'app/controllers/searches_controller.rb', line 39

def show
  @search = Search.find(params[:id])
  authorize! :read, @search

  begin
    # Ask the search to return the current page; it will set result_set_size and pagy_from_search
    page = (params[:page] || 1).to_i
    # Pagy uses params[:items] for per-page count, fall back to saved set_limit
    per_page = params[:items].presence || @search.set_limit
    @results = @search.perform(page, params[:sort], params[:direction], false, per_page, true, false)
    # Create Pagy object with request context for URL generation
    if @search.pagy_from_search
      @pagy = pagy(:offset, @results,
                   count: @search.pagy_from_search.count,
                   page: @search.pagy_from_search.page,
                   limit: @search.pagy_from_search.limit)[0]
    end
  rescue StandardError => e
    Rails.logger.error "[SearchesController#show] perform failed for Search##{@search.id} (#{@search.type}): #{e.class}#{e.message}"
    Appsignal.report_error(e) do |transaction|
      transaction.set_tags(search_id: @search.id.to_s, search_type: @search.type)
    end
    @pagy = nil
    @results = []
  end

  respond_to do |format|
    format.html
  end
end

#unpinObject



123
124
125
126
127
128
129
# File 'app/controllers/searches_controller.rb', line 123

def unpin
  @search_result_id = params[:search_result_id]
  @search_results = SearchResult.where(id: @search_result_id).destroy_all
  respond_to do |format|
    format.turbo_stream
  end
end

#updateObject



144
145
146
147
148
149
150
151
# File 'app/controllers/searches_controller.rb', line 144

def update
  @search = Search.find(params[:id])
  if @search.update(params[:search])
    redirect_to search_path(@search)
  else
    render :edit, status: :unprocessable_entity
  end
end