From 9cb14673015356374a78e4d2f726087f91a41eac Mon Sep 17 00:00:00 2001 From: songtianlun Date: Wed, 22 Jan 2025 14:04:58 +0800 Subject: [PATCH] feat: enhance weather arts and cities features - Add slug column to weather_arts for friendly URLs. - Update weather arts retrieval in the controller to use slug. - Implement region and country filtering in cities index view. - Optimize city queries with scopes for active status and region/country. - Improve UI layout and design for the cities index page. These changes allow better user experience by enabling cleaner URLs for weather arts and facilitating efficient filtering of cities based on selected regions and countries. --- app/admin/weather_arts.rb | 5 + app/controllers/cities_controller.rb | 12 ++ app/controllers/weather_arts_controller.rb | 2 +- app/models/city.rb | 13 +- app/models/weather_art.rb | 12 +- app/views/cities/index.html.erb | 140 ++++++++++++++---- config/routes.rb | 4 +- ...20250122053220_add_slug_to_weather_arts.rb | 6 + db/schema.rb | 17 +-- 9 files changed, 169 insertions(+), 42 deletions(-) create mode 100644 db/migrate/20250122053220_add_slug_to_weather_arts.rb diff --git a/app/admin/weather_arts.rb b/app/admin/weather_arts.rb index 9fd96c3..5e44e0b 100644 --- a/app/admin/weather_arts.rb +++ b/app/admin/weather_arts.rb @@ -1,4 +1,9 @@ ActiveAdmin.register WeatherArt do + controller do + def find_resource + scoped_collection.friendly.find(params[:id]) + end + end # See permitted parameters documentation: # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters # diff --git a/app/controllers/cities_controller.rb b/app/controllers/cities_controller.rb index 6a6367b..1fb8aa8 100644 --- a/app/controllers/cities_controller.rb +++ b/app/controllers/cities_controller.rb @@ -1,6 +1,18 @@ class CitiesController < ApplicationController def index @cities = City.all.order(:name) + @regions = Region.includes(:countries).order(:name) + @cities = City.includes(:country, country: :region).active.order(:name) + + if params[:region] + @current_region = Region.friendly.find(params[:region]) + @cities = @cities.by_region(@current_region.id) + end + + if params[:country] + @current_country = Country.friendly.find(params[:country]) + @cities = @cities.by_country(@current_country.id) + end end def show diff --git a/app/controllers/weather_arts_controller.rb b/app/controllers/weather_arts_controller.rb index 1f5a178..5ae79ec 100644 --- a/app/controllers/weather_arts_controller.rb +++ b/app/controllers/weather_arts_controller.rb @@ -1,6 +1,6 @@ class WeatherArtsController < ApplicationController def show @city = City.friendly.find(params[:city_id]) - @weather_art = @city.weather_arts.find(params[:id]) + @weather_art = @city.weather_arts.friendly.find(params[:slug]) end end diff --git a/app/models/city.rb b/app/models/city.rb index 28b42b9..86d8bf6 100644 --- a/app/models/city.rb +++ b/app/models/city.rb @@ -1,6 +1,6 @@ class City < ApplicationRecord extend FriendlyId - friendly_id :name, use: :slugged + friendly_id :slug_candidates, use: :slugged belongs_to :country has_many :weather_arts, dependent: :destroy @@ -11,10 +11,21 @@ class City < ApplicationRecord delegate :region, to: :country + scope :by_region, ->(region_id) { joins(:country).where(countries: { region_id: region_id }) } + scope :by_country, ->(country_id) { where(country_id: country_id) } + scope :active, -> { where(active: true) } + def to_s name end + def slug_candidates + [ + :name, + [ :country, :name ] + ] + end + def localized_name I18n.t("cities.#{name.parameterize.underscore}") end diff --git a/app/models/weather_art.rb b/app/models/weather_art.rb index fafa5ca..ca9c1e2 100644 --- a/app/models/weather_art.rb +++ b/app/models/weather_art.rb @@ -1,11 +1,21 @@ class WeatherArt < ApplicationRecord - belongs_to :city + extend FriendlyId + friendly_id :weather_date, use: :slugged + belongs_to :city has_one_attached :image validates :weather_date, presence: true validates :city_id, presence: true + def should_generate_new_friendly_id? + weather_date_changed? || city_id_changed? || super + end + + def to_s + "#{city.name} - #{weather_date.strftime('%Y-%m-%d')}" + end + def self.ransackable_associations(auth_object = nil) [ "city", "image_attachment", "image_blob" ] end diff --git a/app/views/cities/index.html.erb b/app/views/cities/index.html.erb index c72a81a..c995f4b 100644 --- a/app/views/cities/index.html.erb +++ b/app/views/cities/index.html.erb @@ -1,35 +1,119 @@ -
-
-

Explore Cities

-

- Discover AI-generated weather art from cities around the world -

+
+ + +
+
+

+ Explore Cities +

+

+ Discover AI-generated weather art from cities around the world +

+
-
- <% @cities.each do |city| %> - <% latest_art = city.weather_arts.last %> -
- <% if latest_art&.image&.attached? %> -
- <%= image_tag latest_art.image, - class: "w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500" %> -
-
- <% end %> + +
+
+ <%= link_to "All Regions", + cities_path, + class: "btn btn-outline #{'btn-primary' unless @current_region}" %> -
-

<%= city.localized_name %>

-
-

Lat: <%= city.latitude %>

-

Long: <%= city.longitude %>

-
-
- <%= link_to "View Weather Art", city_path(city), - class: "btn btn-primary" %> -
-
+ <% @regions.each do |region| %> + <%= link_to region.name, + cities_path(region: region.slug), + class: "btn btn-outline #{'btn-primary' if @current_region == region}" %> + <% end %> +
+ + <% if @current_region %> +
+ <%= link_to "All Countries in #{@current_region.name}", + cities_path(region: @current_region.slug), + class: "btn btn-sm btn-ghost #{'btn-active' unless @current_country}" %> + + <% @current_region.countries.order(:name).each do |country| %> + <%= link_to country.name, + cities_path(region: @current_region.slug, country: country.slug), + class: "btn btn-sm btn-ghost #{'btn-active' if @current_country == country}" %> + <% end %>
<% end %> + + +
+ +
+
+ + +
+
+ <% @cities.each do |city| %> +
+ <% if city.latest_weather_art&.image&.attached? %> +
+ <%= image_tag city.latest_weather_art.image, + class: "w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500" %> +
+
+

+ <%= city.name %> +

+

+ <%= city.country.name %>, <%= city.region.name %> +

+
+
+ <% else %> +
+

<%= city.name %>

+

+ <%= city.country.name %>, <%= city.region.name %> +

+
+ Lat: <%= city.latitude %> + Long: <%= city.longitude %> +
+
+ <% end %> + +
+
+
+ + + + + <%= city.timezone %> +
+ <% if city.latest_weather_art %> +
+ + + + <%= city.latest_weather_art.weather_date.strftime("%b %d, %Y") %> +
+ <% end %> +
+ +
+ <%= link_to "View Details", city_path(city), + class: "btn btn-primary btn-sm" %> +
+
+
+ <% end %> +
\ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 026b4a6..e5c5daa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,8 @@ Rails.application.routes.draw do root "home#index" - resources :cities, only: [ :index, :show ] do - resources :weather_arts, only: [ :show ] + resources :cities, only: [:index, :show] do + resources :weather_arts, path: 'weather', only: [:show], param: :slug end # namespace :admin do diff --git a/db/migrate/20250122053220_add_slug_to_weather_arts.rb b/db/migrate/20250122053220_add_slug_to_weather_arts.rb new file mode 100644 index 0000000..de1b698 --- /dev/null +++ b/db/migrate/20250122053220_add_slug_to_weather_arts.rb @@ -0,0 +1,6 @@ +class AddSlugToWeatherArts < ActiveRecord::Migration[8.0] + def change + add_column :weather_arts, :slug, :string + add_index :weather_arts, :slug, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 3862182..b1c482f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,17 +10,14 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_01_21_020653) do - # These are extensions that must be enabled in order to support this database - enable_extension "pg_catalog.plpgsql" - +ActiveRecord::Schema[8.0].define(version: 2025_01_22_053220) do create_table "active_admin_comments", force: :cascade do |t| t.string "namespace" t.text "body" t.string "resource_type" - t.bigint "resource_id" + t.integer "resource_id" t.string "author_type" - t.bigint "author_id" + t.integer "author_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["author_type", "author_id"], name: "index_active_admin_comments_on_author" @@ -80,7 +77,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_21_020653) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "slug" - t.bigint "country_id", null: false + t.integer "country_id", null: false t.index ["country_id"], name: "index_cities_on_country_id" t.index ["slug"], name: "index_cities_on_slug", unique: true end @@ -89,7 +86,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_21_020653) do t.string "name" t.string "code" t.string "slug" - t.bigint "region_id", null: false + t.integer "region_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["code"], name: "index_countries_on_code", unique: true @@ -119,7 +116,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_21_020653) do end create_table "weather_arts", force: :cascade do |t| - t.bigint "city_id", null: false + t.integer "city_id", null: false t.date "weather_date" t.string "description" t.decimal "temperature" @@ -134,7 +131,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_21_020653) do t.text "prompt" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "slug" t.index ["city_id"], name: "index_weather_arts_on_city_id" + t.index ["slug"], name: "index_weather_arts_on_slug", unique: true end add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"