【Active Storage】ストレージ内にzipファイルを生成してDLする機能作成までにやったこと

## このノートの内容
- クラウドストレージ内のファイルをzipにまとめてDLしたかった
- ファイル処理のメソッドの理解が曖昧だったので、基礎から調べながら行った

## 使用Gem

- [rubyzip](https://github.com/rubyzip/rubyzip)


## 調べた内容

### [File.join](https://docs.ruby-lang.org/ja/latest/method/File/s/join.html)
連結したいディレクトリ名やファイル名を文字列（もしくは文字列を要素に持つ配列）で与えます。
```ruby
File.join("a","b")                        # => "a/b"
```

### [File.open](https://style.potepan.com/articles/27876.html)

Fileクラスから提供されているopenメソッドで、Rubyでファイルを開いたり・作成したりする際に呼び出す処理です。

読み込みや書き込み時にもファイルを開く必要があるため、ファイル関連処理の起点とも言えます。

```ruby
インスタンス = File.open(ファイルパス, 読み書きモード)
```
ファイルパス以外はオプションでの指定となります。
読み書きモードには`'w'`,`'r'`などがありますが、バイナリを扱うものは`'wb'`など`b`がつきます。

### [Zip::File.open](https://www.rubydoc.info/github/rubyzip/rubyzip/Zip/File)

Zipモジュール下ののFile.open。
```ruby
Zip::File.open("my.zip", create: true)
```
上記の例では、zipアーカイブを開きmy.zip（存在しない場合は作成します）内に処理を行います。（"my.zip"に入るのはパスです。）

### [FileUtils.mkdir_p](https://docs.ruby-lang.org/ja/latest/class/FileUtils.html#M_MAKEDIRS)

例えば、

```ruby
FileUtils.mkdir_p('/usr/local/lib/ruby')
```
は以下の全ディレクトリを (なければ) 作成します。

- `/usr`
- `/usr/local`
- `/usr/local/bin`
- `/usr/local/bin/ruby`

よく似たメソッドに `FileUtils.mkdir("ディレクトリ名")`がありますが、こちらは引数に指定されたディレクトリのみを作成します。

### [Pathname.join](https://docs.ruby-lang.org/ja/latest/method/Pathname/i/join.html)
与えられたパス名を連結してくれるメソッドです。

```ruby
path0 = Pathname("/usr")                # Pathname:/usr
path0 = path0.join("bin/ruby")          # Pathname:/usr/bin/ruby
```
`Pathname.join`メソッドについては、下記の記事も面白かったです。

https://qiita.com/jnchito/items/7393c26490b075456154

### [Dir.glob](https://docs.ruby-lang.org/ja/latest/method/Dir/s/=5b=5d.html)

ワイルドカードの展開を行い、パターンにマッチするファイル名を文字列の配列として返します。
`/sample`配下に`hoge.txt`と`fuga.txt`があった時

```ruby
Dir.glob("/sample/*")                      #=> ["hoge.txt", "fuga.txt"]
```
## RubyZipについて

今回はRubyのファイル操作メソッドの他、`RubyZip`というgemを使いました。
[README](https://github.com/rubyzip/rubyzip#rubyzip)によると **Rubyzipは、zipファイルを読み書きするためのrubyライブラリです。** とのこと

### 基本的な使い方

[公式ページ](https://github.com/rubyzip/rubyzip#basic-zip-archive-creation)に記載のコードはこちら

```ruby
require 'rubygems'
require 'zip'

folder = "Users/me/Desktop/stuff_to_zip"
input_filenames = ['image.jpg', 'description.txt', 'stats.csv']

zipfile_name = "/Users/me/Desktop/archive.zip"

Zip::File.open(zipfile_name, create: true) do |zipfile|
  input_filenames.each do |filename|
    # Two arguments:
    # - The name of the file as it will appear in the archive
    # - The original file, including the path to find it
    zipfile.add(filename, File.join(folder, filename))
  end
  zipfile.get_output_stream("myFile") { |f| f.write "myFile contains just this" }
end
```

### OutputStream
こちらの記事によると、クラウドストレージ上でファイルをそのままzipにできるらしい。

https://qiita.com/ham0215/items/cb7386352c4cb0d1120e

上記記事を参考にして作成したコードがこちら。
Rubyのバージョンが記事より古く、番号指定パラメータが使用できなかったので、そこだけ書き換えました。
（あと、rakeタスクを使用しています）

```ruby:some/rake/file.rake
namespace :example_task do
  desc "タスクの説明がここに入ります"
  task zip: :environment do
    tmp_dir = Rails.root.join('tmp', 'example_folder')
    FileUtils.mkdir_p(tmp_dir)
    files_to_zip =  User.find(1).images

    # zipファイル作成
    buffer = Zip::OutputStream.write_buffer do |buffer|
      files_to_zip.each do |file|
        buffer.put_next_entry(file.image.filename.to_s)
        file.image.download { |file_data| buffer.write(file_data) }
      end
    end
    
    # 作成したzipファイルをダウンロード
    File.open(Rails.root.join('tmp/example_folder','downloaded_images.zip'), 'wb') { |file|
      file.write(buffer.string)
    }
  end
end

```

▼番号指定パラメータに関してはこちらで勉強
https://qiita.com/jnchito/items/79f0172e60f237e2c542

## 結果
無事、クラウドストレージ上でzipファイルを作成し、ローカルにDLできました。
実際の運用では

- zipにアーカイブする対象のファイルは数千ファイルあるがそれをどう処理するか
- アーカイブしたファイルはローカルに落とすのではなく、事務の人に共有したいがどうすれば良いか

など他の課題もありましたが、そちらは一旦別の機会に。
ファイル操作について一通り学習する機会となってよかったです。