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 %>
-
+
<%= @cities.count %> <%= 'city'.pluralize(@cities.count) %>
<% if @current_country %>
in <%= @current_country.name %>
@@ -111,11 +113,29 @@
+
+
-
- <%= 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,