Chapter 05無料公開

Tips

Rails開発において、rubyやActiveRecordなどに関するTipsを知ることは生産性を上げる重要な要素だと思います。
ですので、今回扱ったファイルの中から参考になりそうなTipsをいくつかピックアップしてみました。

data_updates.rake

データ更新処理をCUIから実行する役割を担っていたrakeタスクを定義しているファイル
lib/tasks/data_updates.rake

読みやすく数値を扱う「10.minutes」

DataUpdateWorker.perform_in(10.minutes)

データ更新処理を10分後に実行する指定を10.minutesという表記で行なっています。
これはperform_inメソッドの引数として、ActiveSupport::Durationクラスのインスタンスを与えています。

[1] pry(main)> 10.minutes
=> 10 minutes
[2] pry(main)> 10.minutes.class
=> ActiveSupport::Duration

ActiveSupportサポートとはRailsが提供しているコンポーネントで、rubyをより快適に扱うための様々な拡張機能が含まれています。
ActiveSupport::Durationクラスも拡張機能の1つです。
ですので、ruby単体の環境で10.minutesと記述すると、NoMethodError (undefined method minutes for 10:Integer)というエラーが発生してしまいます。
ActiveSupportに関する詳細は Railsガイド ActiveSupportコア拡張機能ページ に記載されています。

ActiveSupport::Durationクラスを使うと10.minutesのように数値を可読性が高い表現で扱えます。
minutes以外にもseconds, hours, days, weeks, months, yearsのようなメソッドも使えます。
また、下記のように数値と同じように演算することができます。

[1] pry(main)> 1.hours < 2.hours
=> true
[2] pry(main)> 6.days > 1.weeks
=> false
[3] pry(main)> 1.minutes - 15.seconds
=> 1 minute and -15 seconds
[4] pry(main)> (1.minutes - 15.seconds).to_i # to_iメソッドでInteger型に変換
=> 45
[5] pry(main)> 10.seconds + 5
=> 15 seconds

環境ごとに処理を切り替える「Rails.env.development?」

Rails.env.development?

development環境かどうかを判別するコードです。
これと同様にproduction?staging?という使い方もできます。

DataUpdateWorker

データ更新処理が実装されていたWorkerクラス
app/workers/data_update_worker.rb

メソッドを動的に呼び出す「public_send」

logger_destination = status == :failed ? :error : :info
Rails.logger.public_send(
  logger_destination,
  "time=#{Time.current.rfc3339}, script=#{file_name}, status=#{status}",
)

scriptのstatusfailedであれば、Rails.loggererrorメソッドを実行して、
それ以外であれば、infoメソッドを実行するというコードです。

public_sendメソッドは全てのクラスから継承されているObjectクラスに実装されているメソッドです。
ですので、全てのクラスのインスタンスから呼び出しことができます。

メソッドの内容は、最初の引数に指定されたシンボル名のメソッドに後続の引数を与えて実行するものです。
ですので、上記の例でstatusfailedである場合は下記のコードと同等となります。

Rails.logger.error(
  "time=#{Time.current.rfc3339}, script=#{file_name}, status=#{status}"
)

DataUpdateScript

scriptファイルの管理する役割を担っていたモデル
app/models/data_update_script.rb

モデルから欲しいattributeだけ取得する「pluck」

db_scripts = DataUpdateScript.pluck(:file_name, :status).to_h

このコードはActiveRecordpluckメソッドとArrayのto_hメソッドを使用しています。

まず、ActiveRecordpluckメソッドは必要なattributesのみを、配列(Array)の形式で取得できます。
下記のように、attributeを1つだけ指定した場合はシンプルな配列を返し、
attributeを複数指定すると、レコードごとのデータを格納した二次元配列を返します。

DataUpdateScript.pluck(:file_name)
# => ["20200826141652_foo", "20200826141657_bar"]

DataUpdateScript.pluck(:file_name, :status)
# => [["20200826141652_foo", "enqueued"], ["20200826141657_bar", "succeed"]

次に二次元配列に対するto_hメソッドですが、これは配列をハッシュに変換してくれます。

DataUpdateScript.pluck(:file_name, :status).to_h
# => {"20200826141652_foo"  => "enqueued", "20200826141657_bar" => "succeed"}

このように、配列の最初の要素がハッシュのキーになり、2つめの要素が値となります。

ディレクトリとファイル名を扱う「Dir.globとPathname」

def filenames
  Dir.glob("*.rb", base: DIRECTORY).map do |f|
    Pathname.new(f).basename(".rb").to_s
  end
end

lib/data_update_scriptsディレクトリからscriptファイルを読み取り、ファイル名の配列を返すメソッドfilenamesのコードです。
まず、Dir.globはディレクトリから指定されたパターンのファイルの配列を取得できるメソッドです。
また、キーワード引数baseに対象のディレクトリを指定できます。

次に、mapブロック内でPathnamebasenameメソッドを使用しています。
Pathnameクラスはパス名を便利に扱えるクラスでして、basenameメソッドはパス名から末尾のファイル名を取得するメソッドです。
basenameメソッドに引数が与えられた場合は、引数の値とファイル名の末尾を比較して、一致した場合は一致した部分を省いた文字列を返します。

path = Pathname.new("foo/bar.rb")
path.basename # => bar.rb
path.basename(".rb") # => bar

文字列をクラス化する「safe_constantize」

def file_class
  "#{self.class::NAMESPACE}::#{parsed_file_name.camelcase}".safe_constantize
end

private

  def parsed_file_name
    file_name.match(/\d{14}_(.*)/)[1]
  end

scriptファイル内に定義されているクラスを返すメソッドfile_classのコードと、そのメソッドから使用されているprivateメソッドparsed_file_nameのコードです。
file_classメソッドは、Workerクラスがscriptを実行する際に使用されます。

まず、privateメソッドparsed_file_nameは、file_nameからタイムスタンプの部分を取り除いた文字列を返します。
ですので、"#{self.class::NAMESPACE}::#{parsed_file_name.camelcase}" の部分は、file_nameが20200826141652_foo_barの場合はDataUpdateScripts::FooBarとなります。

Stringのsafe_constantizeメソッドは、文字列と一致するクラスを返し、クラスが見つからない場合はnilを返します。
同じようなメソッドとして、constantizeメソッドがありますが、こちらはクラスが見つからない場合にエラーが発生します。