Rubyのプログラムをオブジェクト志向で書いてみる

/

導入

以前にRubyで作ったファイルをコピーするためのスクリプトをオブジェクト志向のプログラムに書き換えてみました。

Ruby:FileUtilsで指定したファイルだけをコピーする

#!/usr/bin/env ruby
# coding:utf-8

#外部ファイルの読み込み
require Dir::pwd + '/copy_file.rb'

#インスタンスの作成
copy_file = CopyFile.new("src/", "dist/")

#削除の対象から除外
copy_file.del_exc = ["remain.txt"]
#コピーの対象から除外
copy_file.copy_exc = ["dir1","text3.txt"]

#deleteとcopyメソッドの実行
copy_file.init_copy
# coding:utf-8
require 'fileutils'

class CopyFile
  attr_accessor :src_path,:dist_path,:copy_exc,:del_exc

  def initialize(src, dist, copy_exc=[], del_exc=[])
    @@defulat_exc = ['.','..']
    @src_path,@dist_path = src,dist,copy_exc
    @copy_exc = @@defulat_exc + copy_exc
    @del_exc = @@defulat_exc + del_exc
  end

  def copy_exc=(exc_list)
    @copy_exc = @@defulat_exc + exc_list
  end

  def del_exc=(exc_list)
    @del_exc = @@defulat_exc + exc_list
  end

  def delete()
    puts "Do you really want to delete the #{dist_path}? [Y|n]:"

    response = STDIN.gets
    case response.chomp
    when "Y","Yes","yes"
      Dir::entries(@dist_path).each do |f|
        unless @del_exc.include?(f)

          #コピー先のディレクトリを再帰的に空にする
          #FileUtils.rm_r( Dir.glob("#{dist_path}*") )
          FileUtils.rm_r( @dist_path + f )
          #p dist_path + f
        end
      end
      puts "#{@dist_path} has been cleared."
    else
      puts "It had been canceled"
    end
  end

  def copy()
    #コピー先のディレクトリがなければ作成する
    FileUtils.mkdir_p(@dist_path) unless FileTest.exist?(@dist_path)

    #指定したファイル&ディレクトリをコピーの候補から除外
    #src_pathの中身を再帰的にコピー
    Dir::entries(@src_path).each do |f|
      unless @copy_exc.include?(f)
        src = @src_path + f
        FileUtils.cp_r src, @dist_path
      end
    end

    puts "It has copied files from #{@src_path} to #{@dist_path}"
  end

  def init_copy()
    self.delete()
    self.copy()
  end

end

クラス定義

前回は単純なスクリプトとして実行したい処理を上から順番に書いていくだけでしたが、今回はCopyFileクラスを作成し実行する処理をクラスのメソッドとして定義していきます。

class CopyFile
  attr_accessor :src_path,:dist_path,:copy_exc,:del_exc

  def initialize(src, dist, copy_exc=[], del_exc=[])
    @@defulat_exc = ['.','..']
    @src_path,@dist_path = src,dist,copy_exc
    @copy_exc = @@defulat_exc + copy_exc
    @del_exc = @@defulat_exc + del_exc
  end

  def copy_exc=(exc_list)
    @copy_exc = @@defulat_exc + exc_list
  end

  def del_exc=(exc_list)
    @del_exc = @@defulat_exc + exc_list
  end
  ...
end

変数

クラス変数

スクリプトで扱わないファイルを@@default_excとしてクラス変数で定義しています。
クラス変数は@@で始まる変数でクラス中で定義され、その値はグローバル変数としてクラスやサブクラス、クラスから作られるインスタンスによって共有されます。

インスタンス変数

コピー元のディレクトリ、コピー先のディレクトリ、コピー or 削除しないファイルのリストをそれぞれインスタンス変数に格納しています。
インスタンス変数は@で始まる変数でクラスから作成されたオブジェクト毎に設定されます。

#同じクラスから異なるインスタンス変数の値を持つインスタンスを作成
copy_file = CopyFile.new("src1/", "dist1/")
copy_another = CopyFile.new("src2/", "dist2/")

puts copy_file.src_path
puts copy_another.src_path
#それぞれの変数の値が返される
$ ruby main.rb

src1/
src2/

インスタンス変数/Ruby 2.3.0 リファレンスマニュアル

アクセサ

attr_accessorメソッドでそれぞれの変数に必要なセッターとゲッターを定義しています。

copy_file = CopyFile.new("src/", "dist/")

puts copy_file.src_path
copy_file.src_path = "change_src/"
puts copy_file.src_path
$ ruby main.rb

src/
change_src/

また@copy_excと@del_excはセッターによる値の更新時に、値のリストに@@defulat_excの値を含めるようにそれぞれのセッターメソッドをオーバーライドしています。

def copy_exc=(exc_list)
  @copy_exc = @@defulat_exc + exc_list
end
copy_file = CopyFile.new("src/", "dist/")

copy_file.copy_exc = ["file1","file2"]
p copy_file.copy_exc
$ ruby main.rb

[".", "..", "file1", "file2"]

instance method Module#attr_accessor/Ruby 2.3.0 リファレンスマニュアル

イニシャライズ

initializeメソッドはclassを初期化する、いわゆるコンストラクタと呼ばれる役割のメソッドです。
インスタンスの作成時に@src_path,@dist_path,@copy_exc,@del_excが設定されるよう定義しています。
なお@src_path,@dist_pathの値はインスタンスの作成時に必須ですが、@copy_exc,@del_excは省略された場合には初期値に設定された空のリストが代入されます。

instance method Object#initialize/Ruby 2.3.0 リファレンスマニュアル

メソッド

CopyFileクラスのメソッドとして、指定したディレクトリ内のファイルを消去するメソッドと指定したディレクトリの中身を別のディレクトリにコピーするメソッドを定義します。
また@del_excと@copy_excにそれぞれのファイル名をリストで代入する事で、任意のファイルをメソッドの対象から外す事が出来るようにしています。

delete()

出力先に指定したディレクトリの中身を消去します。
前回はRubyの実行時にコマンドに引数を渡すことで消去が実行されましたが、今回のスクリプトではCopyFileクラスから作られたインスタンスがdeleteメソッドを実行する事でディレクトリ内の消去が行われます。

copy()

指定したディレクトリの入力元から出力先にファイルをコピーします。
前回とコードの内容はほぼ同じです。

init_copy()

deleteとcopyメッソドを続けて呼び出します。

ファイルの分割

今回ファイルをCopyFileクラスを定義しているcopy_file.rbとクラスからインスタンスを作成してメソッドを実行するmain.rbにそれぞれ分割したので、main.rbからcopy_file.rbを読み込んでいます。

Dir::pwd

pwdはDirクラスの特異メソッドでカレントディレクトリのフルパスを文字列として返してくれます。copy_file.rbはmain.rbと同じ階層のディレクトリにあるのでDir::pwdにcopy_file.rbのパスを連結してmain.rbから読み込んでいます。
class Dir/Ruby 2.3.0 リファレンスマニュアル

実行

記事の先頭の内容で作成したmain.rbを実行していきます。

コピーする対象として事前にsrc/内にはdir1とdir2のディレクトリとtext1.txtとtext2.txt、text3.txtのファイルがそれぞれ存在しています。また出力先のdist/にはremain.txtがありますが、これは@del_excによってdeleteメソッドの削除対象から外されています。

この状態でスクリプトを実行すると

$ ruby marin.rb

Do you really want to delete the dist/? [Y|n]:
$ Y
dist/ has been cleared.
It has copied files from src/ to dist/

出力先のディレクトリではremain.txt以外のファイルが削除され、対象となったdir2、text1.txt、text2.txtのコピーが実行されます。

あとがき

以前に書いたコピー用のスクリプトに機能を追加しようと思ったのですが、まずはクラスを使ったオブジェクト指向のプログラミングに書き換えてみました。まだ改善の余地がありそうですが、変数やアクセサなどRubyのクラスの基礎的な機能の復習が出来たので良しとします。
次回はコピー対象のファイルを外部の設定ファイルで指定出来るようにしようかと予定しています。

参照

オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル