Excelダウンロード機能の実装


##ダウンロード機能

今回はRubyXLを使ってStudentモデルのデータをExcel形式でダウンロードする仕組みを作成していきます。
※今回はすでに作成してあるモデルを扱ってます
※自分が分かる様にメモしているだけなので、参考にはならないかも

RubyXLはgemでインストールしてます

```モデル```

```student.rb
  def self.to_xlsx(students) #引数にテーブルを受け渡す
    # binding.pry
    require "rubyXL"
     @workbook = RubyXL::Workbook.new #Excel新規作成
     @worksheet = @workbook[0] #最初のシートを操作

	# Excel上のヘッダー作成
    student_row = %w(氏名 EMAIL 生年月日) #Excelに表示するヘッダーの値を変数に代入
     student_row.each_with_index do |val, row_idx|
       @worksheet.add_cell(0, row_idx, val)
     end

	# 実際の値をDBから取得
    students.each.with_index(1) do |student, row_idx|
  %w(name sex age).each_with_index do |name, col_idx|
    @worksheet.add_cell(row_idx, col_idx, student.__send__(name))
    end
  end
   @workbook.stream.read #バイナリーで値を取得
end
```

```コントローラー```

```students_controller.rb
# indexアクション
  def index
    @q = Student.ransack(params[:q])
    @departments = Department.all
    @students = @q.result(distinct: true)
    excel_dl_format #indexでExcelのダウンロードを扱う為、設定した定義を読み込む
  end

  # Excel_DLフォーマットアクション
   def excel_dl_format
      respond_to do |format|
        format.html
          format.xlsx do
            send_data Student.to_xlsx(@students),#indexアクションの@studentsをsendしている
            filename: "生徒一覧.xlsx".encode(Encoding::Windows_31J)
          end
      end
    end
    
```

```view```

```students/index.html.erb

<%= link_to "生徒一覧ダウンロード", students_path(format: :xlsx), class: "btn btn-info" %>

```

これでRubyXLを使ったDBの値をExcelとしてダウンロードする機能は完成です。

##解説
コードについて解説。
というか、このコードはコピペで取ってきたのだが、理解するまでに時間がかかったので個人的な学習の復習メモ。

###モデルの定義と引数について

```student.rb
def self.to_xlsx(students) ⇦この部分でアクションを定義しているのだが、引数も与えています。
end

その引数はどこから値を持ってきているのかというと

このstudentsと紐づいてます⇨ students.each.with_index(1) do |student, row_idx|
                         %w(name sex age).each_with_index do |name, col_idx|
                         @worksheet.add_cell(row_idx, col_idx, student.__send__(name))

```
引数の名前を
def self.to_xlsx(students) としてますが、
()内の名前は何でもよくて、(hoge)とかでもいいです。
ただしその場合は、

```
hoge.each.with_index(1) do |student, row_idx|
%w(name sex age).each_with_index do |name, col_idx|
@worksheet.add_cell(row_idx, col_idx, student.__send__(name))
```

と、する必要があります。                         

引数がある場合は、必ず何かを参照しているので、中身を確認し、
どこから情報を取ってきているのかを把握することが大事。

###フォーマットについて
コントローラーにて下記のようなフォーマットを定義しているのだが、それの解説

```ruby
  # Excel_DLフォーマットアクション
   def excel_dl_format
      respond_to do |format|
        format.html
          format.xlsx do
            send_data Student.to_xlsx(@students),#indexアクションの@studentsをsendしている
            filename: "生徒一覧.xlsx".encode(Encoding::Windows_31J)
          end
      end
```
####send_data

```ruby
 send_data Student.to_xlsx(@students), ⇦この部分に焦点をあてて解説
```
まず、Student.to_xlsx()という部分、ここでStudentモデルのデータを参照して、
モデルで定義した```def self.to_xlsx(students)```へフォーマット情報を引き渡している役割。だと思う。
イメージそんな感じ。使って感覚的に学んだので、詳しい方教えてください。

※ self.to_xlsx()とStudent.to_xlsx()ってそもそも名前違うけど、そこんとこどうなのって思うかもですが、selfはそのクラス内のという意味なのでself.to_xlsx() == Student.to_xlsx() と考えて良いです

で、Student.to_xlsx(@students)と、引数に@studentsを与えることにより、実際の値を取得できる。

なぜそれで取得できているのかというと、indexアクションで、@studentsにStudentの値をすべて入れているからです！

```
    @q = Student.ransack(params[:q])
    @departments = Department.all
    @students = @q.result(distinct: true)
```

僕の場合、ransackの機能でパラメータを取得しているので、こんな感じになってますが、一般的には

```students_controller.rb
def index
	@students = Student.all
end
```
とかで定義すれば値を取得できます。


####イメージ
キーワードを抜粋するとこんな感じでデータのやりとりをしてるイメージ

```
・self.to_xlsx(students) <==> students.each.with_index(1) do 
・Student.to_xlsx() <==> self.to_xlsx()
・send_data Student.to_xlsx(@students) <==> @students = @q.result(distinct: true)
```

逆にわかりにくいかもだけど、こんな感じ。。。


##ループ(each)について解説

```
student_row.each_with_index do |val, row_idx|
   @worksheet.add_cell(0, row_idx, val)
end
```

RubyXLではループする場合にも、x座標 と　y座標を明確にする必要がある。
上記コードでいうとval = y row_idx = xになる
そしてその中のコードでは@worksheet.add_cell(0, row_idx, val)となっており、x yで示すならば
@worksheet.add_cell(y, x, "値")となる。

ということを踏まえて
@worksheet.add_cell(0, row_idx, val)になっているので

```
y座標が0
x座標がrow_idx
値がval
```

student_rowには　名前、性別、年齢が格納されているので、
最初の行にstudent_rowの値がループされる。という仕組みが出来る。

```ruby
 students.each.with_index(1) do |student, row_idx|
  %w(name sex age).each_with_index do |name, col_idx|
   @worksheet.add_cell(row_idx, col_idx, student.__send__(name))
    end
  end
```

続いてこの部分について解説
studentsをループしています。このstudentsはself.to_xlsx(students)で引数に渡している値になります。

studentsの値がstudentに入って、row_idxでx軸に入ってる値を順に表示していきます。
そして、ループの中で更にループが発生しています。
ここで実際のDBの中身(Studentの値)を取得してます。

```ruby
　　　　　　　　　　　　　　　　　　　　　　　　↓ここでkey指定
  %w(name sex age).each_with_index do |name, col_idx|
   @worksheet.add_cell(row_idx, col_idx, student.__send__(name))
                                                            ↑ここでkey指定 
```

上記のkey(今回はkeyをnameにしてます)を設定してパラメーター取得
col_idxでx軸へ値を入れていく

```ruby
@worksheet.add_cell(row_idx, col_idx, student.__send__(name))
```

上記コードでは、add_cellでセルの操作を取得しつつ
y軸にrow_idx, x軸にcol_idx 値にstudent.__send__(name)
とすることで、大外のrow_idxがループし終わった後に内側のrow_idxが発動。y軸に移動して値を入れていくという仕組みが作れる。