[Rails]予約投稿
はじめに
前回の記事で投稿に公開予約機能を追加しましたが、手動で公開する必要があって不便です。
公開日時が過去で「公開予約」となっている投稿を更新しなくても、自動でステータスを「公開中」に変更できるようにしていきます。
rake
とwhenever
により1時間ごとに走らせ、ステータスを変更していきます。
環境
Rails 7.0.4.3
ruby 3.2.1
scopeを作成する
予約公開されたブログ一覧を取得するためにscope
を作成します。
class Blog < ApplicationRecord
...
scope :scheduled, -> { joins(:status).where("statuses.published_at > ?", Time.current) }
scope :scheduled_for_next_7_days, -> { joins(:status).where("statuses.published_at > ? AND statuses.published_at <= ?", Time.current, 7.days.from_now) }
scope :to_be_published, -> { joins(:status).where(statuses: { name: 2 }).where("statuses.published_at <= ?", Time.current) }
end
irb(main):001:0> Blog.scheduled
Blog Load (0.4ms) SELECT "blogs".* FROM "blogs" INNER JOIN "statuses" ON "statuses"."statusable_type" = $1 AND "statuses"."statusable_id" = "blogs"."id" WHERE (statuses.published_at > '2023-07-29 22:53:01.229405') [["statusable_type", "Blog"]]
=>
[#<Blog:0x0000000116bd6048
id: 17,
title: "予約公開投稿",
content: "テストテスト",
user_id: 1,
created_at: Sat, 29 Jul 2023 16:32:58.770636000 JST +09:00,
updated_at: Sat, 29 Jul 2023 22:51:10.978199000 JST +09:00>]
irb(main):002:0> blog.status
Status Load (0.5ms) SELECT "statuses".* FROM "statuses" WHERE "statuses"."statusable_id" = $1 AND "statuses"."statusable_type" = $2 LIMIT $3 [["statusable_id", 17], ["statusable_type", "Blog"], ["LIMIT", 1]]
=>
#<Status:0x0000000115c7aab8
id: 52,
name: "scheduled",
statusable_type: "Blog",
statusable_id: 17,
created_at: Sat, 29 Jul 2023 20:38:45.674161000 JST +09:00,
updated_at: Sat, 29 Jul 2023 21:58:20.810701000 JST +09:00,
published_at: Sat, 01 Jan 2028 00:00:00.000000000 JST +09:00>
irb(main):003:0> blog.status.update(name:1)
TRANSACTION (0.2ms) BEGIN
Status Exists? (0.4ms) SELECT 1 AS one FROM "statuses" WHERE "statuses"."name" = $1 AND "statuses"."id" != $2 AND "statuses"."statusable_id" = $3 AND "statuses"."statusable_type" = $4 LIMIT $5 [["name", 1], ["id", 52], ["statusable_id", 17], ["statusable_type", "Blog"], ["LIMIT", 1]]
Status Update (0.6ms) UPDATE "statuses" SET "name" = $1, "updated_at" = $2 WHERE "statuses"."id" = $3 [["name", 1], ["updated_at", "2023-07-29 22:58:52.836107"], ["id", 52]]
TRANSACTION (1.5ms) COMMIT
=> true
rakeタスクを作成する
rake
は、タスクを自動化するためのビルドツールです。
rake
は特定の作業を自動化するために、タスク(Task)と呼ばれるRubyスクリプトの集合を定義します。
bin/rails generate task publish_blog
create lib/tasks/publish_blog.rake
scheduled
の投稿をpublihsed
に変更するので名前をchange_scheduled_to_published
にします。
namespace :publish_blog do
desc "予約公開のブログを公開する"
# environmentはDBへアクセスする場合に追記する。今回だとstatus.nameカラムの中身が変わるので必要。
task change_scheduled_to_published: :environment do
models = [Blog, Article, Idea] # 複数のモデルに共通する場合使う
models.each do |model|
if model.reflect_on_association(:status)
scheduled_posts = model.to_be_published
scheduled_posts.each do |post|
post.status.update(name: 2)
end
puts "Successfully updated #{scheduled_posts.count} scheduled #{model.name} posts to published."
else
puts "#{model.name} is not published. Skipping..."
end
end
end
end
reflect_on_association
は、RailsのActive Record
クラスで提供されるメソッドの一つです。
reflect_on_association
メソッドは、特定のクラスに対して関連付けられている他のクラスの情報を取得する際に使用します。具体的には、関連付けのタイプ(belongs_to
、has_one
、has_many
など)、関連するクラス名、外部キーカラムなどの情報を取得できます。
irb(main):014:0> Blog.reflect_on_association(:status)
=>
#<ActiveRecord::Reflection::HasOneReflection:0x00000001157bde80
@active_record=
Blog(id: integer, title: string, content: text, user_id: integer, created_at: datetime, updated_at: datetime),
@klass=nil,
@name=:status,
@options={:as=>:statusable, :dependent=>:destroy, :autosave=>true},
@plural_name="statuses",
@scope=nil,
@type="statusable_type">
irb(main):015:0> Blog.reflect_on_association(:status).macro
=> :has_one
irb(main):016:0> Blog.reflect_on_association(:status).class_name
=> "Status"
irb(main):017:0> Blog.reflect_on_association(:status).foreign_key
=> "statusable_id"
rakeタスクを実行する
bundle exec rake publish_blog:change_scheduled_to_published
Successfully updated 0 scheduled Blog posts to published.
whenever
をインストールする
whenever
は、Railsアプリケーションにおいて定期的に実行したいタスクをスケジュールするためのGemです。whenever
を使用することで、cronジョブを簡単に設定できます。
gem 'whenever', require: false
bundle install
スケジュールファイルを作成する
config
ディレクトリ配下にあるschedule.rb
ファイルを作成してくれます。
bundle exec wheneverize .
[add] writing `./config/schedule.rb'
[done] wheneverized!
# Rails.rootを使用
require File.expand_path(File.dirname(__FILE__) + "/environment")
# cronを実行する環境変数(RAILS_ENVが指定されていないときはdevelopmentを使用)
rails_env = ENV['RAILS_ENV'] || :development
# cronの実行環境を指定(上記で作成した変数を指定)
set :environment, rails_env
# cronのログファイルの出力先指定
set :output, "#{Rails.root}/log/cron.log"
# パスを通すコマンド
job_type :rake, "source /Users/[ユーザー名]/.zshrc; export PATH=\"$HOME/.rbenv/bin:$PATH\"; eval \"$(rbenv init -)\"; cd :path && RAILS_ENV=:environment bundle exec rake :task :output"
# 一時間毎に実行する&タスク名の指定
every :hour do
rake 'publish_blog:change_scheduled_to_published'
end
job_type
のカスタムは、rake
タスクをcronジョブとして設定する際に、環境のセットアップや正しいRubyのバージョンの使用を保証するために使用されます。これにより、タスクが予定通りに実行され、環境の問題を回避することができます。
-
source /Users/[ユーザー名]/.zshrc
: シェルの設定ファイル(.zshrc
)を読み込むコマンドです。ここではZshシェルを使っていることを前提にしています。.zshrc
には、環境変数やパスの設定などが含まれていることが一般的です。 -
export PATH="$HOME/.rbenv/bin:$PATH"
: Rubyのバージョン管理ツールであるrbenv
のパスを環境変数PATH
に追加します。これにより、rbenv
で管理しているRubyのバージョンが使えるようになります。 -
eval "$(rbenv init -)"
:rbenv
の初期化を行います。これにより、Railsアプリケーションがrbenv
で管理しているRubyのバージョンを正しく認識できます。 -
cd :path
: カレントディレクトリをRailsアプリケーションのルートディレクトリに移動します。 -
RAILS_ENV=:environment
::environment
の部分はwhenever
を実行する際に指定した環境名(development
やproduction
など)に置き換わります。これにより、実行するRailsアプリケーションの環境を指定できます。 -
bundle exec rake :task
::task
の部分は実行するタスク名に置き換わります。bundle exec rake
コマンドを使って、RailsアプリケーションのRakeタスクを実行します。 -
:output
: ジョブの標準出力とエラー出力の先を指定します。この部分もwhenever
を実行する際に指定した設定に置き換わります。
whenever
を実行する
# wheneverの設定更新
bundle exec whenever --update-crontab
[write] crontab file updated
# 設定内容にエラーが無いか確認
bundle exec whenever
0 * * * * /bin/bash -l -c 'source /Users/**/.zshrc; export PATH="$HOME/.rbenv/bin:$PATH"; eval "$(rbenv init -)"; cd /Users/chloe/workspace/rails_blog && RAILS_ENV=development bundle exec rake publish_blog:change_scheduled_to_published >> /Users/***/***/***/log/cron.log 2>&1'
## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated.
## [message] Run `whenever --help' for more options.
# 設定されているcronを確認
crontab -l
# Begin Whenever generated tasks for: /Users/***/***/***/config/schedule.rb at: 2023-07-29 23:27:56 +0900
0 * * * * /bin/bash -l -c 'source /Users/**/.zshrc; export PATH="$HOME/.rbenv/bin:$PATH"; eval "$(rbenv init -)"; cd /Users/chloe/workspace/rails_blog && RAILS_ENV=development bundle exec rake publish_blog:change_scheduled_to_published >> /Users/***/***/***/log/cron.log 2>&1'
# End Whenever generated tasks for: /Users/***/***/***/config/schedule.rb at: 2023-07-29 23:27:56 +0900
# crontabの設定を削除
bundle exec whenever --clear-crontab
終わり
予約投稿機能でした。
whenever
を使用することで、rakeタスクのスケジュールを効率的に管理できるので便利ですね。
Discussion