today_ai_weather/app/models/weather_art.rb
songtianlun f43a6b4698 feat: add formatted time method for weather_art
- Introduce a new method `formatted_time` in the `WeatherArt` model
- Update various views to use this new method for date and time display
- Support formatting in local time zones or UTC

This update enhances the time representation for weather data, ensuring
that displayed times can reflect the user's local timezone or remain
fixed at UTC. This improves the usability of the application for
users in different regions.
2025-02-14 13:42:42 +08:00

245 lines
7.5 KiB
Ruby

class WeatherArt < ApplicationRecord
extend FriendlyId
friendly_id :weather_date, use: :slugged
belongs_to :city
has_one_attached :image
has_many :visits, class_name: "Ahoy::Visit", foreign_key: :weather_art_id
has_many :events, class_name: "Ahoy::Event", foreign_key: :weather_art_id
validates :weather_date, presence: true
validates :city_id, presence: true
scope :latest, ->(limit = 100) {
order(created_at: :desc).limit(limit)
}
scope :by_popularity, ->(limit = 100) {
if ActiveRecord::Base.connection.adapter_name.downcase == "sqlite"
joins("LEFT JOIN ahoy_events ON json_extract(ahoy_events.properties, '$.weather_art_id') = weather_arts.id
AND json_extract(ahoy_events.properties, '$.event_type') = 'weather_art_view'")
.group("weather_arts.id")
.select("weather_arts.*, COUNT(ahoy_events.id) as visit_count")
.order("visit_count DESC").limit(limit)
else
joins("LEFT JOIN ahoy_events ON (ahoy_events.properties::jsonb->>'weather_art_id')::integer = weather_arts.id
AND ahoy_events.properties::jsonb->>'event_type' = 'weather_art_view'")
.group("weather_arts.id")
.select("weather_arts.*, COUNT(ahoy_events.id) as visit_count")
.order("visit_count DESC").limit(limit)
end
}
scope :random, ->(limit = 3) {
if ActiveRecord::Base.connection.adapter_name.downcase == "postgresql"
# PostgreSQL 优化版本
order(Arel.sql("RANDOM()")).limit(limit)
elsif ActiveRecord::Base.connection.adapter_name.downcase == "mysql2"
# MySQL 优化版本
order(Arel.sql("RAND()")).limit(limit)
else
# SQLite 或其他数据库的通用版本
order(Arel.sql("RANDOM()")).limit(limit)
end
}
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
def self.ransackable_attributes(auth_object = nil)
[ "city_id", "cloud", "created_at", "description", "feeling_temp", "humidity", "id", "id_value", "precipitation", "pressure", "prompt", "temperature", "updated_at", "visibility", "weather_date", "wind_scale", "wind_speed" ]
end
def view_count
if ActiveRecord::Base.connection.adapter_name.downcase == "sqlite"
Ahoy::Event.where("json_extract(properties, '$.event_type') = 'weather_art_view' AND json_extract(properties, '$.weather_art_id') = ?", self.id).count
else
Ahoy::Event.where("properties::jsonb->>'event_type' = 'weather_art_view' AND (properties::jsonb->>'weather_art_id')::integer = ?", self.id).count
end
end
def formatted_time(type = :date, use_local_timezone = false)
# 获取时区
timezone_info = city&.country&.timezones.present? ?
eval(self.city.country.timezones).first :
{ "zoneName" => "UTC", "gmtOffsetName" => "UTC+00:00" }
# 设置时区对象
time_zone = ActiveSupport::TimeZone[timezone_info["zoneName"]] ||
ActiveSupport::TimeZone["UTC"]
case type
when :date
# 格式化日期
self&.weather_date&.strftime("%B %d, %Y")
when :time
# 获取时间
time = updated_at
if use_local_timezone
# 使用本地时区
local_time = time.in_time_zone(time_zone)
"#{local_time.strftime('%H:%M')} #{timezone_info['gmtOffsetName']}"
else
# 使用 UTC
"#{time.utc.strftime('%H:%M')} UTC"
end
end
end
def image_url
image.attached? ? image.blob : nil
end
def webp_image
return nil unless image.attached?
image.variant(
format: "webp",
saver: {
quality: 100,
strip: true, # 移除元数据以减小文件大小
interlace: "plane" # 渐进式加载
}
)
end
# 添加图片变体处理
PREVIEW_DIMENSIONS = {
big: [ 1792, 1024 ],
medium: [ 896, 512 ],
small: [ 448, 256 ]
}.freeze
def preview_image(size = :medium)
return nil unless image.attached?
width, height = PREVIEW_DIMENSIONS[size] || PREVIEW_DIMENSIONS[:medium]
image.variant(
resize_to_limit: [ width, height ],
format: "webp",
saver: {
quality: 75,
strip: true, # 移除元数据以减小文件大小
interlace: "plane" # 渐进式加载
}
)
end
def watermarked_image
return nil unless image.attached?
overlay_text = create_overlay_text
image.variant(
composite: [ {
input: overlay_text,
gravity: "southeast"
} ]
)
end
private
def create_overlay_text
{
create: {
width: 400,
height: 100,
background: [ 0, 0, 0, 0.5 ] # 半透明黑色背景
},
"svg-overlay": %(
<svg width="400" height="100">
<text x="20" y="40"
style="fill: white; font-family: Arial; font-size: 20px;">
#{city.name} - #{weather_date.strftime('%Y-%m-%d')}
</text>
<text x="20" y="70"
style="fill: white; font-family: Arial; font-size: 20px;">
© todayaiweather.com
</text>
</svg>
)
}
end
def create_text_layer(font_size)
text = [
weather_date.strftime("%Y-%m-%d"),
"#{temperature}°C, #{description}",
"#{city.name}, #{city.country.name}, #{city.country.region.name}",
"© todayaiweather.com"
].join("\n")
{
create: {
width: 600,
height: 200,
background: [ 0, 0, 0, 0 ]
},
"svg-overlay": %(
<svg width="600" height="200">
<style>
.text {
font-family: Arial, sans-serif;
font-size: #{font_size}px;
}
.shadow {
fill: white;
stroke: black;
stroke-width: 2px;
paint-order: stroke fill;
}
</style>
<text x="20" y="#{font_size + 10}" class="text shadow">#{weather_date.strftime('%Y-%m-%d')}</text>
<text x="20" y="#{font_size * 2 + 20}" class="text shadow">#{temperature}°C, #{description}</text>
<text x="20" y="#{font_size * 3 + 30}" class="text shadow">#{city.name}, #{city.country.name}</text>
<text x="20" y="#{font_size * 4 + 40}" class="text shadow">© todayaiweather.com</text>
</svg>
)
}
end
def watermark_command(font_size:, stroke_width:, spacing:)
date_str = weather_date.strftime("%Y-%m-%d")
weather_info = "#{temperature}°C, #{description}"
location_info = "#{city.name}, #{city.country.name}, #{city.country.region.name}"
copyright = "© todayaiweather.com"
"gravity southeast " \
"fill white " \
"font Arial " \
"pointsize #{font_size} " \
"stroke black " \
"strokewidth #{stroke_width} " \
"text 30,#{spacing * 12} '#{copyright}' " \
"text 30,#{spacing * 8} '#{location_info}' " \
"text 30,#{spacing * 4} '#{weather_info}' " \
"text 30,#{spacing} '#{date_str}'"
end
def watermark_text
date_str = weather_date.strftime("%Y-%m-%d")
weather_info = "#{temperature}°C, #{description}"
location_info = "#{city.name}, #{city.country.name}, #{city.country.region.name}"
copyright = "© todayaiweather.com"
[
"text 30,120 '#{copyright}'",
"text 30,80 '#{location_info}'",
"text 30,40 '#{weather_info}'",
"text 30,0 '#{date_str}'"
].join(" ")
end
end