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

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

ただいまの
回答率

90.51%

  • Ruby on Rails

    7292questions

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

ruby on rails DBから取得した値を効率よく加工する方法 setterメソッドの使い方

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,403

hiepita1

score 26

大変お世話になっております。またご質問させていただきます。

前提・実現したいこと

DBから取って来た値を加工してviewに表示したいです。

例えば、column1[金額1],column2[金額2],column3[フラグ]
というレコードをDBからActiverecordで取得して来た時に、フラグの値によって金額の集計方法を変えて、最終的にViewに表示したいです。

発生している問題・エラーメッセージ

取得して来たレコードを、再度foreachで回すことで、実現は可能かと思うんですが、それではパフォーマンス的に問題があるので、この値の加工を、他言語で言う所のsetterメソッドのような形で処理させたいのですが、実現方法がわかりません。

該当のソースコード

//今現在書いているコードです。Modelでのeachが原因で処理速度が落ちています。

controller
 @data = Model.getmethod
end

Model class
  self.def getmethod
  //何十万行のレコードが入ります。
  modeldata = Model.all
  modeldata.each |data|
  data.display_data = data.flg == 1 ? data.kingaku1 - data.kingaku2 : data.kingaku2 - data.kingaku1
  end  

return modeldata
  end
end

//どのように書くのかはわかりませんが、このような感じで、不要なeachを無くしたいです。データベースから値を取得したタイミングで、データの加工も同時にやってしまうようなイメージです。

controller
  @data = Model.getmethod
end

Model class
  self.def getmethod
    //何十万行のレコードが入ります。
    modeldata = Model.all
  end  

  def display_data_setter_method
    return  data.display_data = data.flg == 1 ? data.kingaku1 - data.kingaku2 : data.kingaku2 - data.kingaku1
  end
return modeldata
  end
end

補足情報(言語/FW/ツール等のバージョンなど)

Ruby on Rails5

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • suzukis

    2017/04/05 00:02

    とりあえずコードのインデントが適切に反映されるようにマークアップしてください。編集画面で範囲選択して</>ボタンでできます

    キャンセル

  • hiepita1

    2017/04/07 00:49

    大変失礼致しました。修正しました。

    キャンセル

回答 3

checkベストアンサー

+1

コードが読みにくいので真意をつかみかねますが、やりたいことはこれでいいような気がします。

元のコードに倣ってますのでメソッド名が適切かは検討してください。

class Model < ActiveRecord::Base
  def display_data 
    flg == 1 ? kingaku1 - kingaku2 : kingaku2 - kingaku
  end
end
# @models = Model.all (または .where(なんとかかんとか))

<% @models.each do |model| %>
  <%= model.display_data %>
<% end %>

コールバック使ってロード時に計算する、というのがより質問の趣旨に近いような気がしますが多分やり過ぎです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/04/07 01:06

    インデントが崩れていて読みにくい中、ご回答いただきありがとうございます。
    コードを参考に実装させていただきました所、希望した通りの動作をしました。ありがとうございます。

    >コールバック使ってロード時に計算する、というのがより質問の趣旨に近いような気がしますが多分やり過ぎです。
    仰っているように、質問の趣旨的にはこちらの方が近いです。
    差し支えなければコールバックでの実装方法も教えていただきたいです。また、やり過ぎというのは、コードの可読性が落ちるということでしょうか?あまり一般的な方法ではないということでしょうか?

    キャンセル

  • 2017/04/07 09:35

    after_findというコールバックがあるのでリファレンス読んでください。インスタンスメソッドで十分なことに余計なことをする必要は無い、ということです。

    キャンセル

+1

>>Viewに表示したいです。
indexに一覧を表示したいと言うことでよろしいですか?
ActiveRecordは大変使いやすいですが、数が増えてくるとメモリの関係でもっさりしてきます。
この様な何十万レコードという条件だとActiveRecordの生成コストは馬鹿にならないです。

しかし、実を言うとviewに表示したいだけだったら、Modelを生成する必要はございません。

なので
※pluckはSQLのselect文を実行して結果を配列に格納するmethodです。

@models=Model.pluck(:kingaku1,:kingaku2,:flg)

<% @models.foreach do |model| %>
<%= model[2] == 1 ? model[0] - model[1] : model[1] - model[0] %>
<% end %>

とやればいいのですが。
なんか計算式を見る限り、差額を求めているだけのようなので
いっそのことflgを無視して絶対値だけを表示すれば幸せになれるのではないでしょうか?

@models=Model.pluck("ABS(kingaku1-kingaku2)")#SQLに絶対値を計算させている

<% @models.foreach do |model| %>
<%= number_with_delimiter(model) %>
<% end %>


後suzukisさんも言っていますが
</>
を使うと上のように表示されるので便利ですよ

あと初心者マークをつけると皆さん優しいですよ。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/04/05 15:52

    「モデルは、生成、登録、変更をするためのものです」というのは誤解です。取り出したレコードの値を使って何かをするような処理はモデルのメソッドとして実装するのが基本です。回答のようにビューにロジックを書いてはいけません。あと`pluck`はどうしても必要な場所だけに使い、多用しない方がよいです。特に、複数の値を`pluck`で持ってくるようなコードはインデックスで値を区別しなければならないので可読性が著しく低下します。

    キャンセル

  • 2017/04/05 15:58

    それはその通りですが、何十万行のレコードを処理する際に、
    activerecordの生成コストを払うのはいかがかなと、
    状況と質問者様の、理解度から、そのように表現させていただきましたが
    確かに、嘘を書くのは良くないですよね。誤解のない様修正しておきます。

    キャンセル

  • 2017/04/07 00:59

    ご回答ありがとうございます。activerecordのpluckメソッドを使うことで配列にして返すことができるんですね。 実際のコードではカラムの数も多いので、Modelを生成する方向で進めて行こうと思っていたのですが、そもそも何十万レコードをeachで回すコードに問題がありそうですね。
    SQLでの絶対値を求める方法や、Pluckの動作など、非常に勉強になりました。

    次回から初心者マークもつけていこうと思います。

    キャンセル

+1

既に十分な回答は出ていますが、せっかくなのでSQLでやる例も回答したいと思います。
自分が使っているのがPostgreSQLなので、PostgreSQLの例だけになりますが・・。

とりあえず、動作確認のためにentriesというテーブル名で試しました。

CREATE TABLE entries (
  id serial primary key,
  kingaku1 integer NOT NULL DEFAULT 0,
  kingaku2 integer NOT NULL DEFAULT 0,
  flg integer NOT NULL DEFAULT 1
);

INSERT INTO entries (kingaku1, kingaku2, flg) VALUES(10, 20, 1);
INSERT INTO entries (kingaku1, kingaku2, flg) VALUES(50, 10, 1);
INSERT INTO entries (kingaku1, kingaku2, flg) VALUES(40, 20, 0);
INSERT INTO entries (kingaku1, kingaku2, flg) VALUES(140, 20, 0);

SELECT * FROM entries;
 id | kingaku1 | kingaku2 | flg
----+----------+----------+-----
  1 |       10 |       20 |   1
  2 |       50 |       10 |   1
  3 |       40 |       20 |   0
  4 |      140 |       20 |   0
(4 rows)

SELECT *,
  CASE WHEN flg = 1 THEN kingaku1 - kingaku2
       ELSE kingaku2 - kingaku1
       END
       AS display_data
  FROM entries;

 id | kingaku1 | kingaku2 | flg | display_data
----+----------+----------+-----+--------------
  1 |       10 |       20 |   1 |          -10
  2 |       50 |       10 |   1 |           40
  3 |       40 |       20 |   0 |          -20
  4 |      140 |       20 |   0 |         -120
(4 rows)

SQLではこのようにできているので、あとはこんな感じで。

class Entry < ActiveRecord::Base
  def self.with_display_data
    select=<<SELECT
*,
 CASE WHEN flg = 1 THEN kingaku1 - kingaku2
      ELSE kingaku2 - kingaku1
      END
      AS display_data
SELECT

     Entry.select(select)
  end
end
  • rails console
> puts Entry.with_display_data.all.to_sql
=> 
SELECT *,
 CASE WHEN flg = 1 THEN kingaku1 - kingaku2
      ELSE kingaku2 - kingaku1
      END
      AS display_data
 FROM "entries"
  • view
@entries = Entry.with_display_data.all

<% @entries.each do |entry| %>
  # SELECTでやってる都合上、文字列になるので必要があればto_iする
  <%= entry.display_data.to_i %>
<% end %>

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/04/07 01:14

    ご回答ありがとうございます。
    SQLのCASE文で集計する方法も考えたのですが、可能な限りSQLのベタがきは避けるような方針になっているので、ActiveRecordがサポートしていないCase文などはコード上で処理させるようにしたいと考えています。
    (情報が後出しで申し訳ありません・・・)

    キャンセル

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

  • ただいまの回答率 90.51%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Ruby on Rails

    7292questions

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