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

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

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

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

Ruby on Rails

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

Ruby on Rails 4

Ruby on Rails4はRubyによって書かれたオープンソースのウェブフレームワークです。 Ruby on Railsは「設定より規約」の原則に従っており、効率的に作業を行うために再開発を行う必要をなくしてくれます。

Q&A

3回答

3682閲覧

ネストが深いリレーションの為、eachのネストが発生するスクリプト(本文中にあり)がでます、こういうのは設計を見直すべきなのでしょうか?

qaz3330

総合スコア113

Ruby

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

Ruby on Rails

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

Ruby on Rails 4

Ruby on Rails4はRubyによって書かれたオープンソースのウェブフレームワークです。 Ruby on Railsは「設定より規約」の原則に従っており、効率的に作業を行うために再開発を行う必要をなくしてくれます。

0グッド

0クリップ

投稿2016/05/17 16:26

Userに紐づく、店舗の従業員の給与合計を出力するスクリプトを書きたいです。

以下のようなのを作成したのですが、each がネストしているのがすごく違和感を感じます。

私自身の経験が浅いための勘違いかもしれませんが、
eachがネストしている事自体はさほど問題ないのでしょうか?

sum = 0 User.first.shops.each do |shop| shop.employments.each do |employment| sum += employment.price * employment.hour end end

今後、このデータの数も増えてくるため、
今のうちに設計を見直すべきなのかと思っております。

ご助言頂けると幸いです。
よろしくお願いします。

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

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

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

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

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

guest

回答3

0

SQLで条件を定義するのが困難な場合を除いて、集計はDB側でやるのがセオリーです。集計のためにアプリケーション(Rails)側でループを回すのは典型的なアンチパターンです。

モデルの構造がわからないので想像ですが、

User.first.shops.select('SUM(employments.price * employments.hour) AS total').joins(:employments).first.total

こんな感じで行けるんじゃないでしょうか。

投稿2016/05/18 13:53

suzukis

総合スコア1449

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

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

qaz3330

2016/05/18 14:24

こういうのはアンチパターンなんですね。 勉強になりました。 店舗の従業員の給与合計という風に記載しましたが、実際に運用するのは、もう少し違っていて、合計の金額が数分毎に変わります。 そのような中、今回の仕様上、できれば、ユーザーがアクセスした際に、そのユーザーに紐づく合計金額を出したいです。 もし上記要件を満たそうとした場合に私が考えたのは ・5分毎にDB側で集計をするバッチ処理を書く(5分かどうかは未定ですが) ・今回の使用を考慮し、includeなどでeager_loadingさせる方法で対応させる のがいいのかなと思いました。 やはりこういう場合ですと、バッチ処理のほうがいいのでしょうか?
suzukis

2016/05/19 00:34

随時計算するかバッチにするかは状況次第ですので、どちらが良い悪いはありません。たとえば、全結果の大半が利用されるのか少数しか利用されないのか、などで判断します
guest

0

私だったら、それぞれのモデルクラスに集計用のメソッドを書いて、それを内部で呼び出す形式にします。
で、後々パフォーマンス等々の問題が出たときには、それぞれのメソッドでがさっと取ってくるように変更してゆきます。
まあ、中間のメソッドが必要ないのであれば、最上位のuserに一括でemploymentを取得して集計するようにしますが。

Ruby

1class User < ActiveRecord::Base 2 has_many :shops 3 # 普通に使うとき 4 def total_payment 5 payment = 0 6 self.shops.each {|s| payment += s.total_payment } 7 return payment 8 end 9 10 # 高速化してみる 11 def hi_speed_total_payment 12 user_table = User.arel_table 13 shop_table = Shop.arel_table 14 emp_table = Employment.arel_table 15 join_s = emp_table.join(shop_table, Arel::Nodes::OuterJoin).on(emp_table[:shop_id].eq(shop_table[:id]).join(user_table, Arel::Nodes::OuterJoin).on(shop_table[:user_id].eq(user_table[:id]).join_source 16 total = 0 17 Employment.joins(join_s).where(user_table[:id].eq(self.id).each do |emp| 18 total += emp.total_payment 19 end 20 return total 21 end 22end 23 24class Shop < ActiveRecord::Base 25 belongs_to :user 26 has_many :employments 27 28 def total_payment 29 payment = 0 30 self.employments.each {|emp| payment += emp.total_payment } 31 return payment 32 end 33end 34 35class Employment < ActiveRecord::Base 36 belongs_to :shop 37 38 def total_payment 39 return self.price * self.hour 40 end 41end

Railsを使っていて、パフォーマンスの問題が出るのは、ネストの深さというよりは、DBアクセスのオーバーヘッドにボトルネックがあるので、(eachで回してると、それぞれのeachの中でDBアクセスが発生する)パフォーマンスの問題を考えるなら、どこでDBアクセスが発生しうるのかを考えるのがポイントかと。
eager_loadingとか使ってて、あらかじめ関連インスタンスをメモリにロードしてる場合はループで回しても同じとか、その辺を考える分けです。

可読性の問題とか、その辺を考えるなら、モデルクラスのメソッドをコールして、その内部でループを回す方法が良いかと思います。

投稿2016/05/18 07:26

rifuch

総合スコア1901

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

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

qaz3330

2016/05/18 08:42

ご回答ありがとうございます! 可読性の面やパフォーマンスの面からアドバイスを頂き、大変勉強になりました。 回答を読んでいる中高速化について質問があります。 表現があってるかわかりませんが、 Controllerのところで、includeして、viewに渡します。 (質問文にそこまで書くべきでした・・・。すみません。) @users = User.include(:shops, : employments) このControllerのところでincludeしておけば、高速化したhi_speed_total_paymentメソッドを使用する必要がないのでしょうか? 気になる点としましては、Controllerでincludeしてさらにhi_speed_total_paymentをつかうと、高速化のつもりがさらに遅くなるのかなという点です。 Controllerでincludeするため、total_paymentメソッドを使用するのが今回のようなケースではあっているのかなと素人ながらに思ったのですが、いかがでしょうか?
rifuch

2016/05/18 09:29

おっしゃるとおり、Controllerでincludeしていれば、サンプルの高速化は意味が無いですね。 ちょっと詳しく調べてないので、あやふやですが、Arelが採用された後のRaills(確か3.0以降)は、.eachとか、.allとか、.countなどのメソッドがコールされるまでDBアクセスは発生しません。 で、あらかじめincludesをしてあれば、モデルのメソッドで.eachがコールされたときに、関連モデルも含めて一気に取ってくる仕様になっているはずです。 なので、あらかじめincludesしてあるのであれば、通常のメソッドを使っても大丈夫なはず。 なお、高速化の方は通常の.eachアクセスとは全く別にDBアクセスを発行するので、どっちにしても変わらない、という事になります。(高速化のメソッドコールで余計に1回DBアクセスが走るかな) 実際にDBアクセスが発生するかは、ログを読んで確認するのが一番手っ取り早いかと思います。
qaz3330

2016/05/18 14:25

ありがとうございます。 DBの高速化などの経験がこれまであまりなかったので、大変参考になりました。 ログを読んで確認するというのに、尽きるのですね。 頑張ってログをみて、高速化対応やってみます。
guest

0

他の方がきっとしてくれるであろう回答が勉強になりそうで楽しみです。

私としてはネストの深さより、スクロースせずに見える範囲にループが収まるか否か、という方を重要視する場合が多いと思います。ループが 3 重くらいでも素直な構造のもの (たとえば x, y, z 軸についてのループとか) でループ全体が 20 行で収まっているなら特におかしいとは思いません。実際にはネストの深さというか、条件分岐なども含んだインデントの深さで考えますが。

他のところの処理にもよりますが、今回のようなケースで、クラスを用いてさらにコードのネストを減らすことは有用なことも少なくないと思います。ネストを減らすことが目的ではなく、コードの見通しをよくし、仕様変更などに強くするのが目的になりますが。
具体的には、Shop クラスを作って totalSalary という getter を用意し、Employment クラスを作って salary という getter を用意する、という感じでしょうか。たとえば、支店ごとの合計の他に、支店内の部署ごとの合計も出したい、とかなったときに Employment クラスには手を入れなくても対応できるようになります。

投稿2016/05/17 22:23

unau

総合スコア2468

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問