Rails Diary

プログラミングの学習記録です。

Rakeタスクとwhenever ※ごちゃごちゃメモ

RubyのMakeコマンド = Rake

■ Makeコマンドとは

あるソースファイルから目的のファイルを生成するためのコマンド。

使い方はmakefileというファイルに、ソースファイル名と目的のファイルの生成方法を記述しておき、$ makeとターミナルに入力するだけで、ファイル内のコードが実行され、目的のファイルが作成できるというもの。それのRuby版がRake。

コマンド「make」初心者向けメモ(Hishidama's make-command Memo)

■ Rakeコマンド

Rakeにおいても、まずRailsで定期的に実行したい処理Rakeタスクとして定義しておき、必要な時にコマンド実行することでファイル内に記載した処理が実行される。

■ 自動化

wheneverというgemを使うことで、わざわざ手動でコマンド実行しなくても指定した時間間隔で自動的にRakeコマンドを実行させることができる。

使用例

  • あるブログ制作サイトの投稿ステータス(下書き、公開予定、公開中)があったとする
  • この投稿ステータスはアップロード時間によって決定される
  • アップロード時間が設定されていなければ下書き状態、アップロード時間が現在または過去であれば公開中、未来であれば公開予定といった形
  • アップロード時刻が現在になったタイミングで投稿ステータスを公開中に切り替えるというRakeタスクを作成しておく
  • wheneverを用いて自動化させる

→これにより、製作者が手動で操作しなくても投稿ステータスを適宜変更してくれる機能が作れる

作成方法

$ rails g task article

# lib/tasks/article.rake
namespace :article do
  # 目的の処理を記述
  desc '記事のステータスを変更する'
  task :change_status
    # 処理内容
end
  • lib/tasksディレクトリ配下にarticle.rakeが生成される
  • desc(description)はどういうタスクかの説明文
  • taskにはタスク名と具体的な処理を記載
  • Rakeタスクのファイル名(namespaceで指定される名前空間)に命名規則はないものの、ブロック内のタスクを包括する名詞が望ましい(メールならmail、記事ならarticleみたいに)
    → その方がコマンド実行する際、直感的で分かりやすいから

今回の例で言えば、namespaceがarticleというタスクのinsert_recordというタスクを実行するので

$ rails article:change_status

という形になる。

rails db:migrateも同じような形をしているが実はこれもRakeタスク。

↑の内容と全く関係ないけれどRakeタスクの例

例)youtubeの埋め込み設定で、IDをフォームに入力して貰う仕様だったが、URLをそのまま入力できるように変更。その際、リニューアル前にすでに保存されているidentifierカラム内のデータを変更しなければデータの整合性が保てない。データを一括修正するタスクを作成する。

namespace :fix_embed_youtube_identifier do
  desc 'IDを入力していたidentifierカラムの過去データを一括で修正'
  task update_old_identifier_for_youtube_embed: :environment do
    Embed.youtube.each do |embed|
      embed.update(identifier: "https://youtube/#{embed.identifier}")
    end
  end
end
  • taskの末尾に記載されている:environmentはDBに接続し変更を加える場合に記載が必要
  • 今回はindentifierカラムの内容を書き換えるタスクのため、記載が必要
  • タスクの概略として、元々末尾の相対パス部分だけ保存していたがURL完全体として保存し直すために変数展開で補完して更新している

wheneverとは

crontab管理ライブラリ。wheneverを使うことでcronを動かすことができる。

cronとは(詳細は省く)

【入門】cron(クロン)設定・書き方の基本 - カゴヤのサーバー研究室

  • プログラムを定期的に実行したい時に使う
  • UNIX系のOSに標準で備わっている
  • cronは機能を示す名称で、crontabとはコマンド名またはファイル名

wheneverはcronの設定をrubyの簡単な文法で扱えるようにしたライブラリ。

wheneverの使い方

① インストール

gem 'whenever', require: false
  • wheneverはバックグラウンドで処理されるものであり、Railsとは関係のないプロセス
  • require: falseRailsの実行時に読み込まないようにするための記載
  • まとめるとwheneverはRailsと関係のないプロセスのため、Railsの実行時に読み込まないようにrequire: falseを記載しているよということ
$ bundle exec wheneverise
  • このコマンドにより、config配下にschedule.rbが作成される

wheneverの設定

細かい点で不明なことが多いけれど、ひとまずこういうものと思っておく。

# config/schedule.rb

# Rails.root(Railsメソッド)を使用するために必要
require File.expand_path(File.dirname(__FILE__) + "/environment")

# cronを実行する環境変数
# 環境変数ENV['RAILS_ENV']にセットされている変数または:developmentを指定
# 自分の環境でENV['RAILS_ENV']にすでにdevelopmentがセットされていた
rails_env = ENV['RAILS_ENV'] || :development

# cronを実行する環境変数をセット
set :environment, rails_env

# cron.logの出力先を指定している
# Rails.rootはこのアプリのルート階層が返される
set :output, "#{Rails.root}/log/cron.log"

# 以下、wheneverが上手く機能しなかったので追加

# シェルコマンド設定
# デフォルトはbash -l -c 'command...'で実行される
# 自分の環境は.zshなので設定しておく
set job_template, "/bin/zsh -l -c ':job'"

# .zshrcとrbenvのパスを指定してrakeを定義
# cronは.zshとrbenvの環境で動いてくれないためPATHを通す
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"

job_templateとjob_typeはここら辺参考に

一番上のrequireは何を読み込んでいる?

# config/schedule.rb

# Rails.root(Railsメソッド)を使用するために必要な記載
require File.expand_path(File.dirname(__FILE__) + "/environment")

結論: config/environmentをrequireしている

config/environmentを読み込む理由

インストールの部分と重複するが、wheneverはバックグラウンドで処理するものであり、普段rails sで起動しているプロセスとは別プロセスで実行されるもの。これはRailsとは切り離されたもので、単なるRubyファイルに過ぎない。Railsとは関係のないRubyファイルの中でRailsのメソッドを使いたいので、一番上の行でconfig/environmentを読み込んでいる。

  • __FILE__
  • File.dirname
  • File.expand_path

上記三つの意味をそれぞれ深堀してみることで、File.expand_path(File.dirname(__FILE__) + "/environment")が指し示す意味が分かる◎

■ File.expand_path

pathを絶対パスに展開した文字列を返します。pathが相対パスであればdefault_dirを基準にします。File.expand_path (Ruby 3.1 リファレンスマニュアル)

ちなみにdefault_dirはデフォルトのディレクトリのこと。File.expand_path(path, default_dir = '.')渡されたpathが相対パスだった場合は第二引数のdefault_dirを基準にする(よく分かってない)

  • 絶対パス(フルパス)とは、最上位に位置するディレクトリ(ルートディレクトリ)から、対象のファイルまでの道順全てを記述する方法
  • 相対パスとは、自分の現在位置を基準として説明される対象ファイルの場所。(B町に住む佐藤さんから見て、田中くんはA町にある田中家の2階の部屋にいる。佐藤さんを基準とした田中くんの相対パス)

絶対パス、相対パスとは?使用例からメリット/デメリットまでをまるっと解説
相対パス | 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

__FILE__

現在実行しているファイル。現在のソースファイル名が格納された擬似変数 [Ruby] __FILE__や__dir__って何? - Just do IT

■ File.dirname("dir/file.ext")

filenameの一番後ろのスラッシュより前を文字列として返します。スラッシュを含まないファイル名に対しては"."(カレントディレクトリ)を返します。
クラス:File > クエリ:dirname | るりまサーチ

filenameの一番後ろのスラッシュより前を文字列として返す。小見出しの例で言えば一番後ろのスラッシュより前にある"dir"を返す

まとめると

# config/schedule.rb
require File.expand_path(File.dirname(__FILE__) + "/environment")
  • __FILE__は現在実行しているファイルであるconfig/schedule.rbを指す
  • File.dirname(__FILE__)によってconfig/schedule.rbの一番後ろのスラッシュより前の文字列を取得するため、この場合はconfigが返される
  • 最後、File.expand_pathに"config/environment"というパスが渡されるため、config/environmentの絶対パスが返されている
  • それをrequireしている

スケジュールの記述はgithubを参考に

every 3.hours do # 1.minute 1.day 1.week 1.month 1.year is also supported
  # the following tasks are run in parallel (not in sequence)
  runner "MyModel.some_process"
  rake "my:rake:task"
  command "/usr/bin/my_great_command"
end

every 1.day, at: '4:30 am' do
  runner "MyModel.task_to_run_at_four_thirty_in_the_morning"
end

every 1.day, at: ['4:30 am', '6:00 pm'] do
  runner "Mymodel.task_to_run_in_two_times_every_day"
end

every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot
  runner "SomeModel.ladeeda"
end

every :sunday, at: '12pm' do # Use any day of the week or :weekend, :weekday
  runner "Task.do_something_great"
end

every '0 0 27-31 * *' do
  command "echo 'you can use raw cron syntax too'"
end

# run this task only on servers with the :app role in Capistrano
# see Capistrano roles section below
every :day, at: '12:20am', roles: [:app] do
  rake "app_server:task"
end

https://github.com/javan/whenever#example-schedulerb-file

wheneverが上手く機能しなかった

wheneverが機能しているか確かめるために、スケジュールを1分おきに設定

every 1.minutes do
  rake "{自作したRakeコマンド実行}"
end

上手く動かなかったので、下記の設定を追加

# シェルコマンド設定
# デフォルトはbash -l -c 'command...'で実行される
# 自分の環境は.zshなので設定しておく
set job_template, "/bin/zsh -l -c ':job'"

# .zshrcとrbenvのパスを指定してrakeを定義
# cronは.zshとrbenvの環境で動いてくれないためPATHを通す
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"

https://github.com/javan/whenever#define-your-own-job-types

メモ:環境変数やPATHについて不理解のため調べる

参考にしたサイト

Wheneverは導入が超簡単なcrontab管理ライブラリGemです![Rails 4.2 x Ruby 2.3] | 酒と涙とRubyとRailsと

[Rails]Rails5 wheneverでRakeタスクを定期的に実行

【Rails】Rakeタスクの基本情報と作成・実行方法 - AUTOVICE

Railsでwheneverを使って定期的にタスクを実行する方法 | 人と情報

Wheneverとcronを使用した時にbundlerがおかしくなる