如何在指定时间自动处理数据?及Rails应用每日生成当日Top5故事需求
Hey there! Let's tackle your two questions—scheduled data automation and building that daily Top 5 stories feature for your Rails app—step by step.
Part 1: Implementing Scheduled Data Processing
First, for running tasks at a specific time in Rails, you’ve got two reliable options:
- System-level cron (Linux/macOS) or Task Scheduler (Windows): Stick to native tools if you want to avoid extra gems.
- The
whenevergem: A Rails-friendly wrapper for cron that lets you write scheduled tasks in Ruby syntax (highly recommended for readability and maintainability).
Part 2: Building the Daily Top 5 Stories Feature
Let’s walk through the exact steps to replace your global Top 5 with a daily-updated version:
Step 1: Add Logic to Calculate Daily Top Posts
Add a class method to your Post model to fetch the top 5 posts from a given day. Adjust this based on how you track likes:
# app/models/post.rb class Post < ApplicationRecord # Option 1: If you have a cached `likes_count` column (updated every time a like is added) def self.daily_top_5(date = Date.today) where(created_at: date.beginning_of_day..date.end_of_day) .order(likes_count: :desc) .limit(5) end # Option 2: If you need to count likes in real-time (using a `Like` model that belongs to `Post`) # def self.daily_top_5(date = Date.today) # joins(:likes) # .where(likes: { created_at: date.beginning_of_day..date.end_of_day }) # .group(:id) # .order('COUNT(likes.id) DESC') # .limit(5) # end end
Step 2: Create a Scheduled Job to Cache/Save Daily Top Posts
Using Rails Jobs keeps your code organized and lets you leverage background processing if needed. First, generate a job:
rails generate job DailyTopStories
Choose one of these two options based on your needs:
Option A: Cache the Results (Fast & Simple)
Great for quick reads without needing historical data:
# app/jobs/daily_top_stories_job.rb class DailyTopStoriesJob < ApplicationJob queue_as :default def perform today = Date.today Rails.logger.info "Calculating daily top 5 stories for #{today}" top_posts = Post.daily_top_5 # Store in Rails cache with a date-specific key Rails.cache.write("daily_top_posts_#{today}", top_posts, expires_in: 1.day) Rails.logger.info "Successfully cached daily top posts: #{top_posts.pluck(:id).join(', ')}" end end
Option B: Save to a Dedicated Table (For Historical Data)
Use this if you want to keep records of past daily Top 5s:
# First generate a model to store daily rankings rails generate model DailyTopPost post:references rank:integer date:date rails db:migrate
Then update the job:
# app/jobs/daily_top_stories_job.rb class DailyTopStoriesJob < ApplicationJob queue_as :default def perform today = Date.today Rails.logger.info "Calculating daily top 5 stories for #{today}" # Clear existing entries for the day to avoid duplicates DailyTopPost.where(date: today).destroy_all # Save each top post with its rank Post.daily_top_5.each_with_index do |post, index| DailyTopPost.create!(post: post, rank: index + 1, date: today) end Rails.logger.info "Successfully saved daily top posts for #{today}" end end
Step 3: Schedule the Job to Run Daily
We’ll use the whenever gem for this (it’s way simpler than writing raw cron):
- Add the gem to your
Gemfile:
# Gemfile gem 'whenever', require: false
- Install the gem and initialize the schedule file:
bundle install wheneverize .
- Edit
config/schedule.rbto set your desired run time (e.g., 23:59 to capture all posts from the day):
# config/schedule.rb every 1.day, at: '23:59' do runner "DailyTopStoriesJob.perform_now" end
- Update your server’s cron tab:
whenever --update-crontab
If you prefer not to use whenever, add this cron command directly to your system (replace /path/to/your/app with your Rails app’s path):
59 23 * * * cd /path/to/your/app && bundle exec rails runner "DailyTopStoriesJob.perform_now"
Step 4: Update Your Controller & View
Modify your controller to fetch the daily Top 5:
For Cache Option:
# app/controllers/posts_controller.rb class PostsController < ApplicationController def top_stories # Fallback to real-time calculation if cache is missing (e.g., first run) @posts = Rails.cache.read("daily_top_posts_#{Date.today}") || Post.daily_top_5 end end
For Dedicated Table Option:
# app/controllers/posts_controller.rb class PostsController < ApplicationController def top_stories @posts = DailyTopPost.where(date: Date.today).order(rank: :asc).pluck(:post) end end
Your existing view code doesn’t need major changes—since @posts now holds the day’s Top 5, this will work just fine:
<% posts.each_with_index do |post, index| %>
<% end %>...
Quick Tips
- Server Time: Double-check your server’s timezone to ensure the job runs at the correct time.
- Logging: The job includes logger statements to help you debug if things go wrong. Check
log/production.log(or development log) for details. - Cache Backend: If you’re using caching, make sure you have a backend like Redis or Memcached set up (Rails defaults to file-based cache, which isn’t ideal for production).
内容的提问来源于stack exchange,提问作者Noor Ali Khan

