LoginSignup
4
7

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-06-12

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

使用ファイルなど

  • 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品ずつ表示されるようになっているので、

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

ほう、困った

最初に思いついたこと

ブランドの方が簡単なのでブランドから取り組んでみました

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編集済

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編集済

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追記
上記結論で書いたコードでは、トップページに表示したいブランドが変更した場合にコードを書き直さなければならなくなります。

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

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

models/brand.rb
class Brand < ApplicationRecord
  has_many :products
end
models/product.rb
class Product < ApplicationRecord
  scope :recent, -> { order("id DESC").limit(4) }
end
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

4
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7