diff --git a/app/controllers/resources_controller.rb b/app/controllers/resources_controller.rb index 939002dd..2ede95b6 100644 --- a/app/controllers/resources_controller.rb +++ b/app/controllers/resources_controller.rb @@ -13,6 +13,14 @@ def show render json: ResourcesPresenter.present(resource) end + def search + p params[:query] + result = Resources::Search.perform(params.require(:query), lat_lng: lat_lng, scope: resources) + # root: :resources is required if Algolia is used since Jsonite won't wrap + # the result with a :resources key. + render json: ResourcesPresenter.present(result, root: :resources) + end + def create resources = clean_resources_params.map { |r| Resource.new(r) } fix_resources(resources) diff --git a/app/services/resources/search.rb b/app/services/resources/search.rb new file mode 100644 index 00000000..8e7b5358 --- /dev/null +++ b/app/services/resources/search.rb @@ -0,0 +1,69 @@ +module Resources + module Search + def self.perform(query, lat_lng: nil, scope: Resource) + strategy = if Rails.configuration.x.algolia.enabled + AlgoliaStrategy + else + DatabaseStrategy + end + strategy.perform(query, lat_lng, scope) + end + + class SearchStrategy + def self.perform(_query, _lat_lng, _scope) + raise NotImplementedError + end + end + + class DatabaseStrategy < SearchStrategy + SEARCH_CONFIG = 'english' + + # SEARCH_COLUMNS maps table names to an array of the columns in + # that table to search. + SEARCH_COLUMNS = { + resources: %i[ + name + short_description + long_description + website + ].freeze, + services: :long_description, + notes: :note, + categories: :name + }.freeze + + CLAUSE = SEARCH_COLUMNS.map do |t, cols| + Array.wrap(cols).map do |c| + "to_tsvector('#{SEARCH_CONFIG}', coalesce(#{t}.#{c}, ''))" + end + end.flatten.join('||').freeze + + def self.perform(query, lat_lng, scope) + sort = lat_lng.blank? ? 'resources.name' : Address.distance_sql(lat_lng) + + scope + .left_outer_joins(services: [:categories]) + .left_outer_joins(:notes) + .left_outer_joins(:categories) + .left_outer_joins(:addresses) + .where("#{CLAUSE} @@ plainto_tsquery('#{SEARCH_CONFIG}', ?)", query) + .group('resources.id, addresses.latitude, addresses.longitude') + .order(sort) + end + end + + class AlgoliaStrategy < SearchStrategy + def self.perform(query, lat_lng, scope) + opts = if lat_lng.blank? + {} + else + { + aroundLatLng: "#{lat_lng.lat}, #{lat_lng.lng}", + aroundRadius: 20_000 # Meters + } + end + scope.algolia_search(query, opts) + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 6accd433..15565422 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,6 +18,7 @@ resources :resources do resources :notes, only: :create collection do + get :search get :count end