From 799f3222a9a8dae702dd156bc19449fa231a9f23 Mon Sep 17 00:00:00 2001 From: songtianlun Date: Wed, 12 Feb 2025 14:00:03 +0800 Subject: [PATCH] feat: add city search functionality - Implement search_by_name scope in City model - Add SearchController for handling search input - Include _search_city partial in cities index view - Update cities_controller to filter cities based on search query This commit introduces a new feature that allows users to search for cities by name using an input field. The search is implemented as a scope in the City model, and it is integrated into the existing CitiesController. A dedicated SearchController manages the input submission with a debouncing mechanism for better performance. The search field is rendered in the cities index view, enhancing user interactivity and experience. --- app/controllers/cities_controller.rb | 4 +++ app/javascript/controllers/index.js | 2 ++ .../controllers/search_controller.js | 13 ++++++++ app/models/city.rb | 9 ++++++ app/views/cities/_search_city.html.erb | 30 +++++++++++++++++++ app/views/cities/index.html.erb | 28 ++++++++++++++--- 6 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 app/javascript/controllers/search_controller.js create mode 100644 app/views/cities/_search_city.html.erb diff --git a/app/controllers/cities_controller.rb b/app/controllers/cities_controller.rb index 6f616d9..a89ab26 100644 --- a/app/controllers/cities_controller.rb +++ b/app/controllers/cities_controller.rb @@ -6,6 +6,10 @@ class CitiesController < ApplicationController @regions = Region.includes(:countries).order(:name) @cities = City.includes(:country, country: :region).order(:name) + if params[:query].present? + @cities = @cities.search_by_name(params[:query]) + end + if params[:region] @current_region = Region.friendly.find(params[:region]) @cities = @cities.by_region(@current_region.id) if @current_region diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index e099ff0..f0137f8 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -6,7 +6,9 @@ import { application } from "./application" import HelloController from "./hello_controller" import PhotoSwipeLightBoxController from "./photo_swipe_lightbox_controller" import FlashMessageController from "./flash_controller" +import SearchController from "./search_controller" application.register("hello", HelloController) application.register("photo-swipe-lightbox", PhotoSwipeLightBoxController) application.register("flash", FlashMessageController) +application.register("search", SearchController) diff --git a/app/javascript/controllers/search_controller.js b/app/javascript/controllers/search_controller.js new file mode 100644 index 0000000..bcfed0e --- /dev/null +++ b/app/javascript/controllers/search_controller.js @@ -0,0 +1,13 @@ +// app/javascript/controllers/search_controller.js +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["input"] + + submit() { + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.element.requestSubmit() + }, 300) + } +} \ No newline at end of file diff --git a/app/models/city.rb b/app/models/city.rb index c66e452..be8d9b1 100644 --- a/app/models/city.rb +++ b/app/models/city.rb @@ -97,6 +97,15 @@ class City < ApplicationRecord .order("COUNT(ahoy_events.id) DESC, cities.name ASC").limit(limit) end } + scope :search_by_name, ->(query) { + return all if query.blank? + + decoded_query = URI.decode_www_form_component(query).downcase + + where( + "LOWER(cities.name) LIKE :query", query: "%#{decoded_query}%" + ) + } def to_s diff --git a/app/views/cities/_search_city.html.erb b/app/views/cities/_search_city.html.erb new file mode 100644 index 0000000..9264924 --- /dev/null +++ b/app/views/cities/_search_city.html.erb @@ -0,0 +1,30 @@ +
+ <%= form_with url: cities_path, method: :get, class: "relative", data: { controller: "search" } do |f| %> +
+
+ + + +
+ <%= f.text_field :query, + value: params[:query] ? URI.decode_www_form_component(params[:query]) : nil, + class: "w-full pl-12 pr-4 py-3 rounded-full bg-base-200/50 backdrop-blur-sm border border-base-300 focus:outline-none focus:ring-2 focus:ring-primary/50", + placeholder: "Search cities...", + autocomplete: "off", + data: { + action: "input->search#submit", + search_target: "input" + } %> + <% if params[:query].present? %> + <%= link_to cities_path, class: "absolute inset-y-0 right-0 flex items-center pr-4" do %> + + + + <% end %> + <% end %> +
+ + <%= f.hidden_field :region, value: params[:region] if params[:region] %> + <%= f.hidden_field :country, value: params[:country] if params[:country] %> + <% end %> +
\ No newline at end of file diff --git a/app/views/cities/index.html.erb b/app/views/cities/index.html.erb index b94cdf2..b80509e 100644 --- a/app/views/cities/index.html.erb +++ b/app/views/cities/index.html.erb @@ -22,6 +22,8 @@ Discover AI-generated weather art from cities around the world

+ <%= render 'cities/search_city' %> + <% if featured_art %>
@@ -99,7 +101,7 @@ <% end %>
-
+
+ +
-
- <%= render partial: 'city', collection: @cities %> -
+ + <% if @cities.empty? %> +
+
+ + + +

No cities found

+

+ Try adjusting your search or filters to find what you're looking for. +

+
+
+ <% else %> + +
+ <%= render partial: 'city', collection: @cities %> +
+ <% end %> <%= render 'shared/pagination', collection: @cities,