Some checks are pending
Docker / docker (push) Waiting to run
- Replaced Leaflet with OpenLayers for improved map rendering - Added OpenLayers CSS and removed Leaflet CSS - Updated map controller to use OpenLayers API - Added marker icon in public directory - Added scopes and associations for weather art in City model This change migrates the map display from Leaflet to OpenLayers, providing better performance and more features. It also introduces new model associations for weather arts, allowing to sort cities by latest weather updates.
218 lines
6.9 KiB
Ruby
218 lines
6.9 KiB
Ruby
class City < ApplicationRecord
|
|
extend FriendlyId
|
|
friendly_id :slug_candidates, use: :slugged
|
|
belongs_to :country, optional: true
|
|
belongs_to :state, optional: true
|
|
|
|
has_many :weather_arts, dependent: :destroy
|
|
|
|
has_many :visits, class_name: "Ahoy::Visit", foreign_key: :city_id
|
|
has_many :events, class_name: "Ahoy::Event", foreign_key: :city_id
|
|
|
|
delegate :region, to: :country
|
|
|
|
validates :name, presence: true
|
|
validates :latitude, presence: true
|
|
validates :longitude, presence: true
|
|
|
|
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) }
|
|
scope :inactive, -> { where(active: false) }
|
|
|
|
# 在 City 模型中
|
|
scope :by_popularity, ->(period = :year, limit = 100) {
|
|
# 根据时间周期确定时间范围
|
|
start_time =
|
|
case period.to_sym
|
|
when :day
|
|
1.day.ago
|
|
when :week
|
|
1.week.ago
|
|
when :month
|
|
1.month.ago
|
|
when :year
|
|
1.year.ago
|
|
else
|
|
1.year.ago
|
|
end
|
|
|
|
# 根据数据库类型构建不同的查询
|
|
base_query = if ActiveRecord::Base.connection.adapter_name.downcase == "sqlite"
|
|
joins(<<-SQL.squish)
|
|
LEFT JOIN ahoy_events ON#{' '}
|
|
json_extract(ahoy_events.properties, '$.city_id') = cities.id
|
|
AND json_extract(ahoy_events.properties, '$.event_type') = 'city_view'
|
|
AND ahoy_events.time > '#{start_time}'
|
|
SQL
|
|
else
|
|
joins(<<-SQL.squish)
|
|
LEFT JOIN ahoy_events ON#{' '}
|
|
(ahoy_events.properties::jsonb->>'city_id')::integer = cities.id
|
|
AND ahoy_events.properties::jsonb->>'event_type' = 'city_view'
|
|
AND ahoy_events.time > '#{start_time}'
|
|
SQL
|
|
end
|
|
|
|
base_query
|
|
.group("cities.id")
|
|
.select("cities.*, COUNT(ahoy_events.id) as visit_count")
|
|
.order("visit_count DESC")
|
|
.limit(limit)
|
|
}
|
|
|
|
scope :least_popular_active, ->(limit = 100) {
|
|
if ActiveRecord::Base.connection.adapter_name.downcase == "sqlite"
|
|
active
|
|
.joins("LEFT JOIN ahoy_events ON json_extract(ahoy_events.properties, '$.city_id') = cities.id
|
|
AND json_extract(ahoy_events.properties, '$.event_type') = 'city_view'")
|
|
.group("cities.id")
|
|
.select("cities.*, COUNT(ahoy_events.id) as visit_count")
|
|
.order("visit_count ASC, cities.name ASC").limit(limit)
|
|
else
|
|
active
|
|
.joins("LEFT JOIN ahoy_events ON (ahoy_events.properties::jsonb->>'city_id')::integer = cities.id
|
|
AND ahoy_events.properties::jsonb->>'event_type' = 'city_view'")
|
|
.group("cities.id")
|
|
.select("cities.*, COUNT(ahoy_events.id) as visit_count")
|
|
.order("visit_count ASC, cities.name ASC").limit(limit)
|
|
end
|
|
}
|
|
scope :most_popular_inactive, ->(limit = 100) {
|
|
if ActiveRecord::Base.connection.adapter_name.downcase == "sqlite"
|
|
where(active: false)
|
|
.joins("LEFT JOIN ahoy_events ON json_extract(ahoy_events.properties, '$.city_id') = cities.id
|
|
AND json_extract(ahoy_events.properties, '$.event_type') = 'city_view'")
|
|
.group("cities.id")
|
|
.select("cities.*, COUNT(ahoy_events.id) as visit_count")
|
|
.order("COUNT(ahoy_events.id) DESC, cities.name ASC").limit(limit)
|
|
else
|
|
where(active: false)
|
|
.joins("LEFT JOIN ahoy_events ON (ahoy_events.properties::jsonb->>'city_id')::integer = cities.id
|
|
AND ahoy_events.properties::jsonb->>'event_type' = 'city_view'")
|
|
.group("cities.id")
|
|
.select("cities.*, COUNT(ahoy_events.id) as visit_count")
|
|
.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}%"
|
|
)
|
|
}
|
|
|
|
# 定义 latest_weather_art 关联
|
|
has_one :latest_weather_art, -> { order(weather_date: :desc) },
|
|
class_name: 'WeatherArt'
|
|
|
|
# 包含最新天气艺术的 scope
|
|
scope :with_latest_weather_art, -> {
|
|
includes(:latest_weather_art)
|
|
}
|
|
|
|
# 只获取有最新天气艺术的城市
|
|
scope :has_weather_art, -> {
|
|
joins(:weather_arts).distinct
|
|
}
|
|
|
|
# 按最新天气更新时间排序
|
|
scope :order_by_latest_weather, -> {
|
|
joins(:weather_arts)
|
|
.group('cities.id')
|
|
.order('MAX(weather_arts.weather_date) DESC')
|
|
}
|
|
|
|
# 获取最近24小时内更新过天气的城市
|
|
scope :recently_updated, -> {
|
|
joins(:weather_arts)
|
|
.where('weather_arts.weather_date > ?', 24.hours.ago)
|
|
.distinct
|
|
}
|
|
|
|
|
|
def to_s
|
|
name
|
|
end
|
|
|
|
def slug_candidates
|
|
[
|
|
:name,
|
|
[ :country, :name ]
|
|
]
|
|
end
|
|
|
|
def localized_name
|
|
I18n.t("cities.#{name.parameterize.underscore}", default: name)
|
|
end
|
|
|
|
def full_name
|
|
"#{name}, #{country}"
|
|
end
|
|
|
|
def should_generate_new_friendly_id?
|
|
name_changed? || super
|
|
end
|
|
|
|
def self.ransackable_associations(auth_object = nil)
|
|
[ "weather_arts" ]
|
|
end
|
|
|
|
def self.ransackable_attributes(auth_object = nil)
|
|
[ "active", "country_id", "created_at", "id", "id_value", "last_image_generation", "last_weather_fetch", "latitude", "longitude", "name", "priority", "region", "slug", "timezone", "updated_at" ]
|
|
end
|
|
|
|
def last_weather_fetch
|
|
# latest_weather_art&.created_at
|
|
Rails.cache.fetch("city/#{id}/last_weather_fetch", expires_in: 1.hour) do
|
|
latest_weather_art&.created_at
|
|
end
|
|
end
|
|
|
|
def last_image_generation
|
|
# latest_weather_art&.image&.created_at
|
|
Rails.cache.fetch("city/#{id}/last_image_generation", expires_in: 1.hour) do
|
|
latest_weather_art&.image&.created_at
|
|
end
|
|
end
|
|
|
|
def latest_weather_art
|
|
weather_arts.order(weather_date: :desc).first
|
|
end
|
|
|
|
def view_count(period = :day)
|
|
start_time = case period
|
|
when :day
|
|
1.day.ago
|
|
when :week
|
|
7.days.ago
|
|
when :month
|
|
1.month.ago
|
|
when :year
|
|
1.year.ago
|
|
else
|
|
1.year.ago
|
|
end
|
|
if ActiveRecord::Base.connection.adapter_name.downcase == "sqlite"
|
|
# Ahoy::Event.where("json_extract(properties, '$.event_type') = 'city_view' AND json_extract(properties, '$.city_id') = ?", self.id).count
|
|
Ahoy::Event
|
|
.where("time >= ?", start_time)
|
|
.where("json_extract(properties, '$.event_type') = 'city_view' AND json_extract(properties, '$.city_id') = ?",
|
|
self.id)
|
|
.count
|
|
else
|
|
# Ahoy::Event.where("properties::jsonb->>'event_type' = 'city_view' AND (properties::jsonb->>'city_id')::integer = ?", self.id).count
|
|
Ahoy::Event
|
|
.where("time >= ?", start_time)
|
|
.where("properties::jsonb->>'event_type' = 'city_view' AND (properties::jsonb->>'city_id')::integer = ?",
|
|
self.id)
|
|
.count
|
|
end
|
|
end
|
|
end
|