質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Q&A

解決済

1回答

1022閲覧

Controllerの特定の処理を別の箇所に切り出したい

begenner

総合スコア79

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

0グッド

0クリップ

投稿2021/07/17 04:32

編集2021/07/17 04:41

現在Railsアプリを作成しています。

現在1つのControllerのindexアクションの行数が多くなりすぎており、モデルに切り出そうと考えたのですが、
切り出したい処理が
・特定のクラスに属するインスタンスではない
ためモデルに処理を切り出せません。

以下のようなコードの場合はどこに、どのように処理を切り出せばよいでしょうか?
複数のレコード情報をもつインスタンスにメソッドを作成できればいいのですが、それに関する該当するような記事を見つけることができませんでした。

もしわかる方がいらっしゃればご教授いただきますようよろしくお願いいたします(m_ _m)

動作環境

Ruby: 2.5.7
Rails: 5.1.7

期待する動作

  • Controllerから処理の部分を別の部分へ切り出せること
  • 切り出す前と同じ動作ができること

処理の内容

todaysmenusが存在する場合
・@stocks,@todaysmenusをそれぞれrawmaterial_idごとに合算して、まとめる
・@stocksから@todaysmenusの内容を引き算した結果を@stocks_not_plan_to_consumeに格納する
todaysmenusが存在しないばあい場合
・@stocksのkey(rawmaterial_id)とvalue(quantity)を@stocks_not_plan_to_consumeに格納する

試したこと

  • modelにget_remaining_stocksというメソッドを作成して切り出そうとしたが、`NoMethodError at /stocks

undefined method get_remaining_stocks'のエラーが発生する

該当しそうなコード

app/controllers/stocks_controllers.rb(1/4)

ruby

1# app/controllers/stocks_controllers.rb 2 def index 3 @stocks = current_user.stocks.includes(:rawmaterial, { rawmaterial: :unit }).unused 4 @todaysmenus = current_user.todaysmenus.includes(:cuisine, cuisine: :foodstuffs).search_in_today 5 6 # begin 切り出したいコード 7 # 残るstocksがある場合は@stocks_not_plan_to_consumeに値が格納されている 8 @stocks_not_plan_to_consume = {} 9 # @stocks_not_plan_to_consume = get_remaining_stocks(@stocks, @todaysmenus) 10 if @todaysmenus.present? 11 stocks = Hash[@stocks.pluck(:rawmaterial_id, :quantity).to_h.map { |key, val| [key, Rational(val)] }] 12 todaysmenus = @todaysmenus.create_hash_todaysmenus(@todaysmenus) 13 stocks_results = @stocks.remaining_amount(stocks, todaysmenus) 14 @stocks_not_plan_to_consume = stocks_results 15 else 16 @stocks_not_plan_to_consume = Hash[@stocks.pluck(:rawmaterial_id, :quantity).to_h.map { |key, val| [key.to_s, Rational(val)] }] 17 end 18 # end 切り出したいコード 19 20 end

app/models/stock.rb(2/4)

ruby

1# app/models/stock.rb 2 def self.remaining_amount(stocks, todaysmenus = nil) 3 stocks_result = {} 4 stocks.each do |st| 5 todaysmenus.each do |tm| 6 stocks_result.store(st[0].to_s, st[1] - tm[1]) if st[0] == tm[0] 7 end 8 stocks_result.store(st[0].to_s, st[1]) unless stocks_result.key?(st[0].to_s) 9 end 10 stocks_result.delete_if { |_key, value| value <= 0 } 11 end 12 13 # begin 追加した部分(NoMethodError at /stocks undefined method get_remaining_stocks が発生する) 14 def get_remaining_stocks(stocks, todaysmenus) 15 stocks_remainings = {} 16 binding.pry 17 if todaysmenus.present? 18 stocks = Hash[stocks.pluck(:rawmaterial_id, :quantity).to_h.map { |key, val| [key, Rational(val)] }] 19 binding.pry 20 todaysmenus = @todaysmenus.create_hash_todaysmenus(@todaysmenus) 21 stocks_results = @stocks.remaining_amount(stocks, todaysmenus) 22 binding.pry 23 @stocks_not_plan_to_consume = stocks_results 24 else 25 @stocks_not_plan_to_consume = Hash[@stocks.pluck(:rawmaterial_id, :quantity).to_h.map { |key, val| [key.to_s, Rational(val)] }] 26 end 27 end 28 # end 追加した部分

app/models/todaysmenu.rb(3/4)

ruby

1# app/models/todaysmenu.rb 2 def self.create_hash_todaysmenus(todaysmenus) 3 quantities = [] 4 todaysmenus.each do |tm| 5 c = tm.cuisine 6 # binding.pry 7 c.foodstuffs.each do |fs| 8 quantities.push([fs.rawmaterial_id, Rational(fs.quantity) * tm.serving_count]) 9 end 10 end 11 tmp_grouped_todaysmenus = quantities.group_by do |r| 12 r.first 13 end 14 tmp_grouped_todaysmenus.each do |k, v| 15 tmp_grouped_todaysmenus[k] = v.inject(0) do |sum, arr| 16 sum += Rational(arr.last) 17 end 18 end 19 grouped_todaysmenus = tmp_grouped_todaysmenus 20 end

app/models/todaysmenu.rb(4/4)

ruby

1# db/schema.rb 2 3ActiveRecord::Schema.define(version: 2021_07_16_093528) do 4 5 create_table "cuisines", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| 6 t.string "name", null: false, comment: "料理名" 7 t.bigint "genre_id" 8 t.integer "difficulty", limit: 1, default: 0, null: false, comment: "料理の難易度(enumで、低・中・高)" 9 t.string "calories", comment: "摂取カロリー" 10 t.integer "cooking_time", null: false, comment: "調理時間" 11 t.string "description" 12 t.string "main_image", null: false, comment: "メイン画像" 13 t.datetime "created_at", null: false 14 t.datetime "updated_at", null: false 15 t.index ["genre_id"], name: "index_cuisines_on_genre_id" 16 t.index ["name"], name: "index_cuisines_on_name", unique: true 17 end 18 19 create_table "foodcategories", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| 20 t.string "name", null: false, comment: "食材区分名(肉、野菜、魚、炭水化物など)" 21 t.datetime "created_at", null: false 22 t.datetime "updated_at", null: false 23 t.index ["name"], name: "index_foodcategories_on_name", unique: true 24 end 25 26 create_table "foodstuffs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| 27 t.string "quantity", comment: "数量" 28 t.bigint "cuisine_id", comment: "料理id" 29 t.bigint "rawmaterial_id", comment: "原材料id" 30 t.integer "row_order" 31 t.datetime "created_at", null: false 32 t.datetime "updated_at", null: false 33 t.index ["cuisine_id"], name: "index_foodstuffs_on_cuisine_id" 34 t.index ["rawmaterial_id"], name: "index_foodstuffs_on_rawmaterial_id" 35 end 36 37 create_table "genres", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| 38 t.string "name", null: false 39 t.datetime "created_at", null: false 40 t.datetime "updated_at", null: false 41 t.integer "cuisines_count", default: 0, null: false 42 end 43 44 create_table "rawmaterials", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| 45 t.string "name", null: false, comment: "原材料名" 46 t.string "hiragana" 47 t.bigint "unit_id" 48 t.bigint "foodcategory_id" 49 t.integer "expiry_period", default: 1, null: false 50 t.integer "foodstuffs_count", default: 0, null: false 51 t.datetime "created_at", null: false 52 t.datetime "updated_at", null: false 53 t.index ["foodcategory_id"], name: "index_rawmaterials_on_foodcategory_id" 54 t.index ["name"], name: "index_rawmaterials_on_name", unique: true 55 t.index ["unit_id"], name: "index_rawmaterials_on_unit_id" 56 end 57 58 create_table "stocks", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| 59 t.string "quantity", null: false 60 t.bigint "rawmaterial_id" 61 t.bigint "user_id" 62 t.date "rotted_at", default: "2021-07-16", null: false 63 t.datetime "consumed_at" 64 t.boolean "abandoned", null: false 65 t.datetime "created_at", null: false 66 t.datetime "updated_at", null: false 67 t.index ["rawmaterial_id"], name: "index_stocks_on_rawmaterial_id" 68 t.index ["user_id"], name: "index_stocks_on_user_id" 69 end 70 71 create_table "todaysmenus", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| 72 t.bigint "cuisine_id" 73 t.bigint "user_id" 74 t.integer "serving_count", default: 1, null: false 75 t.integer "cooked_when" 76 t.datetime "created_at", null: false 77 t.datetime "updated_at", null: false 78 t.index ["cuisine_id"], name: "index_todaysmenus_on_cuisine_id" 79 t.index ["user_id"], name: "index_todaysmenus_on_user_id" 80 end 81 82 create_table "units", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| 83 t.string "name" 84 t.datetime "created_at", null: false 85 t.datetime "updated_at", null: false 86 end 87 88 add_foreign_key "cuisines", "genres" 89 add_foreign_key "favorites", "cuisines" 90 add_foreign_key "favorites", "users" 91 add_foreign_key "rawmaterials", "units" 92 add_foreign_key "stocks", "rawmaterials" 93 add_foreign_key "stocks", "users" 94 add_foreign_key "todaysmenus", "cuisines" 95 add_foreign_key "todaysmenus", "users" 96end

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

こういう時はPORO(Plain Old Ruby Object)で対応するのセオリーだと思います。
POROという大仰な呼ばれ方をしていますが、要するに何も継承していない普通のRubyクラスの事です。

ruby

1class StocksNotPlanToConsume 2 3 def initialize(stocks:, todaysmenus:) 4 @stocks = stocks 5 @todaymenus = todaysmenus 6 end 7 8 def run 9 # begin 切り出したいコード 10 # 残るstocksがある場合は@stocks_not_plan_to_consumeに値が格納されている 11 @stocks_not_plan_to_consume = {} 12 # @stocks_not_plan_to_consume = get_remaining_stocks(@stocks, @todaysmenus) 13 if @todaysmenus.present? 14 stocks = Hash[@stocks.pluck(:rawmaterial_id, :quantity).to_h.map { |key, val| [key, Rational(val)] }] 15 todaysmenus = @todaysmenus.create_hash_todaysmenus(@todaysmenus) 16 stocks_results = @stocks.remaining_amount(stocks, todaysmenus) 17 @stocks_not_plan_to_consume = stocks_results 18 else 19 @stocks_not_plan_to_consume = Hash[@stocks.pluck(:rawmaterial_id, :quantity).to_h.map { |key, val| [key.to_s, Rational(val)] }] 20 end 21 # end 切り出したいコード 22 23 # 戻り値 24 @stocks_not_plan_to_consume 25 end 26 27end

ruby

1def index 2 @stocks = current_user.stocks.includes(:rawmaterial, { rawmaterial: :unit }).unused 3 @todaysmenus = current_user.todaysmenus.includes(:cuisine, cuisine: :foodstuffs).search_in_today 4 5 @stocks_not_plan_to_consume = StocksNotPlanToConsume.new( 6 stocks: @stocks, 7 todaysmenus: @todaysmenus 8 ).run 9end

このクラス「StocksNotPlanToConsume」の置き場所ですが、app/以下に任意のフォルダを作って配置すれば自動的に読み込まれるはずです。
※ただし、クラス名と同じファイル名でないと読み込まれないというRailsのルールがあるので注意です。
単語のつなぎでアンダースコアにするのを忘れずに。
私の例だと、StocksNotPlanToConsumeというクラス名なので、stocks_not_plan_to_consume.rbといファイル名でなければいけません。
app/任意のフォルダ/stocks_not_plan_to_consume.rb に配置するという事です。

app/modelsに置かないほうが良いと思います。
ActiveRecordとは別であるという事を分かりやすくするために。
フォルダ名は何でもいいんですが、logicsやservices, commandsあたりがよく使われるのではないでしょうか。

ちなみに自分はcommandsをよく使うのでコマンド実行という意味で実行するメソッド名をrunとしていますが、他の名前でもいいです。
コンストラクタで引数を受け取って、実行メソッドで処理をして戻り値を返してもらえればなんでも良いので。

投稿2021/07/17 09:54

mingos

総合スコア4025

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

begenner

2021/07/17 11:37

回答いただきありがとうございます。 POROという概念を知らなかったので知ることができて嬉しく思います。 手法についても知ることができてよかったです。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問