[Rails]instance_variable_set　変数名を変えて繰り返し使いたいメソッドをスッキリしてみた

某フリマサイトのコピーサイトを作っているときに直面した課題を解決できたので記事にします。


#使用ファイルなど
- products_controller.rb
- products/index.html.haml
- productsテーブル
- categoriesテーブル
- brandsテーブル


#前提条件
**brandsテーブル**

| id | name |
|:-:|:-:|
| 1 | ナイキ  |
| 2  | ルイヴィトン  |
|  3 | シュプリーム  |

**productsテーブル**

| id | name | brand_id |
|:-:|:-:|:-:|
| 1 | キャップ  | 1 |
| 2  | サッカーシューズ  | 1 |
|…|（省略）|…|


#解決したい課題
**・同じメソッドを何回も使いたいが、ビュー側では違う変数名で渡して区別したい**

フリマサイトのトップページには

カテゴリ別

- レディース新着
- メンズ新着
- ベビー・キッズ新着

ブランド別

- ナイキ新着
- ルイヴィトン新着
- シュプリーム新着

などが4品ずつ表示されるようになっているので、

- 各カテゴリごとにやりたいことは同じ→同じメソッドを使いたい
- だけど中身はそれぞれ参照するカテゴリが違うため当然中身も変わってくる
- さらにビューで変数も変えないと区別して表示できない

ほう、困った

# 最初に思いついたこと
ブランドの方が簡単なのでブランドから取り組んでみました

```ruby:products.controller.rb
class ProductsController < ApplicationController
  def nike_products
    @nikes = Product.where(brand_id: 1).order("id DESC").limit(4)
  end
end
# ナイキがbrandsテーブルのid1番に登録されていると仮定
```
これで商品の中からブランドがナイキのものを取得できました。
でもこれって、ルイヴィトンでもシュプリームでもメソッド定義して、変数命名して、やるのかな？
ブランドが100個あったら100個定義するの？そんなはずは…

#(執筆中さらに思いついたこと)
**※2019/06/26編集済**

```ruby:products.controller.rb
class ProductsController < ApplicationController
  def some_action_method
    @nikes = brand_info(1)
    @louisvuittons = brand_info(2)
    @supremes = brand_info(3)
  end

  private

  def brand_info(num)
    Product.where(brand_id: num).order("id DESC").limit(4)
  end
end
```
これならメソッド1つで実現はできます。
ですが、ブランドの数だけ変数が増えてしまいますよね。100個なら…？:sweat_smile:

#結論：instance_variable_setメソッドで変数定義時に別の変数を組み込む
> obj.instance_variable_set(name, val)

> instance_variable_setメソッドは、レシーバのインスタンス変数に値を設定します。引数nameにはインスタンス変数の名前を:@titleや"@title"のようにシンボルか文字列で渡します。引数valには変数に設定する値を渡します。戻り値は設定した値です。

instance_variable_setメソッドはもともとインスタンス変数に値を設定するためのもの。
ただ、インスタンス変数名側にも別の変数を使えるという性質を利用します。

**※2019/06/26編集済**

```ruby:products.controller.rb
 1. class ProductsController < ApplicationController
 2.   def brand_info_set
 3.     (1..3).each do |num|
 4.       products = Product.where(brand_id: num).order("id DESC").limit(4)
 5.       instance_variable_set("@brand_no#{num}", products)
 6.     end
 7.   end
 8. end
```
4行目で`brand_id: 1`の商品4つを`products`に代入
5行目で先ほどの`products`を`@brand_no1`という変数に代入することに成功しました。
これを`(1..3).each`で繰り返すため、
`@brand_no1`,`@brand_no2`,`@brand_no3`というインスタンス変数を定義することができました。
中身はナイキ、ルイヴィトン、シュプリームがそれぞれ入っています。
これをview側に渡せば、ブランド分けも完璧ですね！

#もっといい方法を教えてもらいました！
**※2019/06/26追記**
上記結論で書いたコードでは、トップページに表示したいブランドが変更した場合にコードを書き直さなければならなくなります。

```ruby:products_controller.rb
class ProductsController < ApplicationController
  def brand_info_set
    Brand.all.each do |brand|
      products = brand.products.order("id DESC").limit(4)
      id = brand.id
      instance_variable_set("@brand_no#{id}", products)
    end
  end
end
```

これならブランドテーブルに存在するものだけを変数化することができ、都度変数定義する必要がないためスッキリしました。
さらにスコープをモデルに書けば、

```ruby:models/brand.rb
class Brand < ApplicationRecord
  has_many :products
end
```

```ruby:models/product.rb
class Product < ApplicationRecord
  scope :recent, -> { order("id DESC").limit(4) }
end
```

```ruby:products_controller.rb
class ProductsController < ApplicationController
  def brand_info_set
    Brand.all.each do |brand|
      products = brand.products.recent
      id = brand.id
      instance_variable_set("@brand_no#{id}", products)
    end
  end
end
```
`order("id DESC").limit(4)」`を他でも使いたい場合に`recent`だけで済むのでもっとスッキリですね！
課題としては変数名にidを用いているため、直接的ではないという点でしょうか。
より良い案がありましたらコメントお願いします！


###ちなみに
カテゴリも分けることができたのですが、ブランドの応用系であることと、
instance_variable_setの説明がメインの記事のため今回は割愛します。

#参考記事
Rubyリファレンス:instance_variable_set (Object)
　https://ref.xaio.jp/ruby/classes/object/instance_variable_set
[Ruby]Rubyの変数の名前に変数の中身を使う方法
　http://portaltan.hatenablog.com/entry/2015/07/03/161157
Railsでよく利用する、Scopeの使い方。
　https://qiita.com/ngron/items/14a39ce62c9d30bf3ac3