応用編4

2021.5.7

記事ステータスの追加

 

記事を作成、公開する

公開 公開待ち1時間ごとに指定可能 下書き

下書きにし、公開日を指定し、更新ボタン、公開ボタン→公開待ち

公開日を過去の日に設定すると自動的に公開になる

 

rakeタスク

Rakeとはrubyで処理内容を定義できるビルドツール。

Rakeが実行する処理内容を「Rakeタスク」と呼び、定義する場所を「Rakefile」と呼ぶ。

railsを使用して、実行して欲しい処理を「Rakefile」に記載しておき、設定したタイミングでそのファイルを実行する。

独自のタスクを作成することが可能

参照

Rails のコマンドラインツール - Railsガイド

 

cron・whenever

cron

基本的に、定期的に何かのプログラムを自動で実行する場合にはcronというものを使用する。cronlinux(Unix系)のOS上にある、定期実行のためのデーモンプロセス(利用者の操作とは無関係に処理を行うバックグラウンドプロセスの一種)のことを指す。

cronに「この時間にこのプログラムを実行しておいて!」「毎日この時間になったらこのプログラムをお願い!」ということをお願い(crontabに記述)しておけば、決まった時間に実行してくれる。

参照

Railsで定期実行する方法 wheneverを使ってcronを設定すると比較的楽にできます - 独学エンジニアの記録帳

 

whenever gem

本来は「○○時になったら○○を実行」するcronというプロセスを自前で用意する必要があるが、wheneverを使えばcronに対して命令を行うcrontabに記述する内容をruby言語で書けるようになる。

→cronの設定を自分で書く代わりに、このgemを使用すると、比較的簡単に定期実行ができる。

 

参照

whenever/README.md at e916b58f29ea4da76de9ddc5aca8c85a5f29f897 · javan/whenever · GitHub

 

find_eachメソッド

<説明>

・分割してレコードを取得し、1件ずつ処理

・デフォルトでは1000件ずつ処理

・大きなデータを持つモデルなどを処理する時に使用

<使い方>

モデル.find_each([オプション]) do |i|

処理内容

end

参照

find_each | Railsドキュメント

rails/batches.rb at f33d52c95217212cbacc8d5e44b5a8e3cdc6f5b3 · rails/rails · GitHub

 

enumで自動生成されるメソッド

enum state: { draft: 0, published: 1, publish_wait: 2 }
 
↑これによって↓が使えるようになる
article = Article.new(state: :draft)
 
article.state #=> draft
article.draft #=> true
article.published? #=> false
 
article.piblished! #=> stateをpublishedに変更
 
Article.state #=> {draft: 0, published:1, publish_wait: 2}
# article = Article.new(state:Article.state[:draft]} こんな感じで使える
 
Article.published
# publishedの検索ができる
# Article.where(state: :published)でも可

参照

Rails enumについてまとめておく - Qiita

 

Rakeタスクを作成する

$ bundle exec rails g tasks article_state

article.rbarticle_state.rake(作成したファイル)に実行したい処理を記入する

 

article.rb

・「公開待ち」のpublish_waitを追記

・scopeでpast_publishedの範囲を今より過去の時間になっているものと定義

enum state: { draft: 0, published: 1, publish_wait: 2 }
#publish_waitを追加する
enumに追加するときは既存のデータへの影響がないように一番右に追加する❶
scope :past_published, -> { where('published_at <= ?', Time.current) }

❶既存データdraft:0, published:1としてデータが登録されている為、%i[draft , publish_wait, publish]と記載すると、draft: 0 , publish_wait:1 , publish: 2 になってしまう。

 

article_state.rake 

・実行したい処理を記入する

namespace :article_state do
#article_stateの部分合わせる
desc '公開予定日時を過ぎた記事を公開状態にする'
task update_article_state: :environment do
Article.publish_wait.past_published.find_each(&:published!)
#「公開待ち」の記事を取得上で設定した範囲指定1件ずつ処理「公開」に更新
end
end

 

・Rakeタスクの一覧を表示して登録されているか確認

$ bundle exec rake --tasks

 

wheneverの導入

・作成したRakeタスクを1時間毎に実行させる為に導入する。

gemfileにgemを追加し、bundle installする

$ bundle exec wheneverize . を実行するとconfig/schedule.rbというファイルが作成される

 

config/schedule.rb

・rakeタスクを1時間毎に走らせる処理を書く

# Rails.rootを使用するために必要
require File.expand_path(File.dirname(__FILE__) + '/environment')
# cronを実行する環境変数
rails_env = ENV['RAILS_ENV'] || :development
# cronを実行する環境変数をセット
set :environment, rails_env
# cronのログの吐き出し場所
set :output, "#{Rails.root}/log/cron.log"
# rakeタスクを1時間ごとに実行
every :hour do
rake 'article_state:update_article_state'
#上から持ってきている
end

 

(app/assets/javascripts/admin.js)

・公開日時を1時間ごとにしか設定できないようにする

format: 'YYYY-MM-DD HH:00'

 

cronをアップデートする

・config/schedule.rbに書いた設定を反映する前にまず、cronを書き換えずに更新内容を確認する

 

ターミナルで

$ bundle exec whenever

設定内容に問題がなければ、↓でcrontabを更新する

$ bundle exec whenever --update-crontab

↓でRakeタスクが問題なく実行できているか確認

$ rake status_task:update_status_task

参照

【Rails忘備録】Rakeタスクを用いてステータス更新するの巻 - 君が代

1時間ごとに記事のステータス処理 - Ruby on Railsの備忘録

Railsでwheneverを使ってバッチを回す!(rakeタスク、cron) - Qiita

 

記事ステータスの判定ロジックについて

モデルにメソッドを定義する

(app/models/article.rb)

def publishable? #公開時間が過ぎているか?
Time.current >= published_at
end

def message_on_published #flashメッセージ定義
if published?
'記事を公開しました'
elsif publish_wait?
'記事を公開待ちにしました'
end
end

def adjust_state #draft(下書き)であればreturnする。公開時間が過ぎていれば公開
過ぎていなければ公開待ち
return if draft?

self.state = if publishable? #一番上で定義したpublishable?使用
:published
else
:publish_wait
end
end
end

 

モデル↑で定義したものを使用している

公開ボタン用

(app/controllers/admin/articles/publishes_controller.rb)

def update
@article.published_at = Time.current unless @article.published_at?
#記事の公開日時がなければ現在時間を代入する
@article.state = @article.publishable? ? :published : :publish_wait
#三項演算子 a ? b : c →aが真であればb、さもなくばc
#公開ステータスが公開時間が過ぎていれば公開。過ぎていなければ公開待ち
if @article.valid?
Article.transaction do
@article.save!
end
flash[:notice] = @article.message_on_published #article.rbで定義済
redirect_to edit_admin_article_path(@article.uuid)
else
flash.now[:alert] = 'エラーがあります。確認してください。'

@article.state = @article.state_was if @article.state_changed?
#「真(true)」なら実行したい処理 if 条件式
#記事のステータスに変更があれば、変更前のものに変更代入する。
#state_was 変更前の値を取得する(column_was)
#state_changed? 特定のカラムの変更をチェック(column_chenged?)
render 'admin/articles/edit'
end
end

 

更新ボタン用

(app/controllers/admin/articles_controller.rb)

def update
authorize(@article)
#authorizeについて Punditをなるべくやさしく解説する - Qiita
@article.assign_attributes(article_params)
#assign_attributes→特定のattributeを変更する。オブジェクトの変更
のみでDBには保存されない。

update_attibutesとassign_attributes違い - Qiita

@article.adjust_state
#article.rbで定義済
#下書きの状態であればreturnする、公開時間が過ぎていれば公開。過ぎていなければ公開待ち。
if @article.save
flash[:notice] = '更新しました'
redirect_to edit_admin_article_path(@article.uuid)
else
render :edit
end
end

 

RSpecでrakeタスクのテスト

(spec/rake_helper.rb) 

テスト全体に及ぼす設定を定義する。

require 'rails_helper'
require 'rake'
RSpec.configure do |config|
config.before(:suite) do
Rails.application.load_tasks #←Rakeタスクの読み込み
#lib/tasksは以下の全てのrakeファイルをソートしてloadしている。
end

config.before(:each) do
Rake.application.tasks.each(&:reenable) #Rakeタスクの再実行
#テストにおいて、テスト間の結合性をなくすということは重要。→テスト間に、
全てのタスクがよばれた履歴を抹消する。
#reenableメソッド 自身をもう一度実行できるようにする(毎回exampleの実行前に実行?)
end
end

実行に順序がある(最近はallをcontext、eachをexampleとも表現する)

before :suite
before :context
before :example
after  :example
after  :context
after  :suite

実行タイミング

suite: テスト(RSpecコマンド)の実行前(afterだと後)に呼ばれる。=最初(afterなら最後)の一回のみだけ呼ばれる。

all: それが記述されているcontext内のexampleの実行前(後)に一度呼ばれる。=context単位で実行される。

each: それが記述されているcontext内のexampleの実行前(後)に一度呼ばれる。=example単位で実行される。

 

参照

`before` and `after` hooks - Hooks - RSpec Core - RSpec - Relish

Transactions - RSpec Rails - RSpec - Relish

RailsでRakeタスクをシンプルかつ効果的にテストする手法 - Qiita

DatabaseCleanerの実装と使い所 - Qiita

 

(spec/lib/tasks/update_article_state_spec.rb)

Rake.application経由で呼び出したタスクを実行する。

require 'rake_helper'

describe 'article_state:update_article_state' do
subject(:task) { Rake.application['article_state:update_article_state'] }
before do
create(:article, state: :publish_wait, published_at: Time.current - 1.day)
create(:article, state: :publish_wait, published_at: Time.current + 1.day)
create(:article, state: :draft)
end


xit 'update_article_state' do
expect { task.invoke }.to change { Article.published.size }.from(0).to(1)
end
end