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

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

ただいまの
回答率

90.52%

  • Ruby

    7653questions

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

  • Ruby on Rails 4

    2435questions

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

Ruby on Rails 会話一覧で最新のメッセージを一件だけ表示させたい

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 659

riamk

score 32

前提・実現したいこと

rails4 ローカル環境

現在メッセージ機能を実装してラインのようなメッセージのやりとりができるようにしたいと思っています。   

機能自体は実装できたのですが、会話一覧ページ(誰とメッセージのやり取りをしているか表示するラインのトーク一覧のようなページ)で最新のメッセージを一件取得する方法が分かりません。

conversations : 会話情報を格納するテーブル
messages : 会話の個々のメッセージを格納するテーブル
として実装しました。

該当のソースコード

conversations/index.html.erbここで最新のメッセージを一件だけ取得したい

<div class="container message">
  <div class="wrapper col-md-8 col-md-offset-2 col-sm-10 message-index">
      <h2>メッセージ一覧</h2>

      <% @conversations.each do |conversation| %>
      <div class="row conversation-list">
        <% if conversation.target_user(current_user).present? %>
          <%= link_to conversation.target_user(current_user).name, conversation_messages_path(conversation)%>
        <% end %>
      </div>
      <% end %>
  </div>
</div>

messages_controller.rb

class MessagesController < ApplicationController
  before_action :authenticate_user!

  before_action do
    @conversation = Conversation.find(params[:conversation_id])
  end

  def index
    @messages = @conversation.messages
    if @messages.length > 10
      @over_ten = true
      @messages = @messages[-10..-1]
    end

    if params[:m]
      @over_ten = false
      @messages = @conversation.messages
    end

    if @messages.last
      if @messages.last.user_id != current_user.id
       @messages.last.read = true
      end
    end

    @message = @conversation.messages.build
  end

  def create
    @message = @conversation.messages.build(message_params)
    if @message.save
      redirect_to conversation_messages_path(@conversation)
    end
  end

  private
    def message_params
      params.require(:message).permit(:body, :user_id)
    end

messageモデル

class Message < ActiveRecord::Base
  belongs_to :conversation
  belongs_to :user

  validates_presence_of :body, :conversation_id, :user_id
  def message_time
    created_at.strftime("%m/%d/%y at %l:%M %p")
  end
end

conversations_controller.rb

class ConversationsController < ApplicationController

  before_action :authenticate_user!

  def index
    @users = User.all
    @conversations = Conversation.all
  end

  def create
    if Conversation.between(params[:sender_id], params[:recipient_id]).present?
      @conversation = Conversation.between(params[:sender_id], params[:recipient_id]).first
    else
      @conversation = Conversation.create!(conversation_params)
    end

    redirect_to conversation_messages_path(@conversation)
  end

  private
    def conversation_params
      params.permit(:sender_id, :recipient_id)
    end
end

Conversationモデル

class Conversation < ActiveRecord::Base
  belongs_to :sender, foreign_key: :sender_id, class_name: 'User'
  belongs_to :recipient, foreign_key: :recipient_id, class_name: 'User'
  has_many :messages, dependent: :destroy
  validates_uniqueness_of :sender_id, scope: :recipient_id
  scope :between, -> (sender_id,recipient_id) do
    where("(conversations.sender_id = ? AND conversations.recipient_id =?) OR (conversations.sender_id = ? AND  conversations.recipient_id =?)", sender_id,recipient_id, recipient_id, sender_id)
  end

  def target_user(current_user)
    if sender_id == current_user.id
      User.find(recipient_id)
    elsif recipient_id == current_user.id
      User.find(sender_id)
    end
  end
end

データベーステーブル

  create_table "messages", force: :cascade do |t|
    t.text     "body"
    t.integer  "conversation_id"
    t.integer  "user_id"
    t.boolean  "read",            default: false
    t.datetime "created_at",                      null: false
    t.datetime "updated_at",                      null: false
  end
  add_index "messages", ["conversation_id"], name: "index_messages_on_conversation_id", using: :btree
  add_index "messages", ["user_id"], name: "index_messages_on_user_id", using: :btree

  create_table "conversations", force: :cascade do |t|
    t.integer  "sender_id"
    t.integer  "recipient_id"
    t.datetime "created_at",   null: false
    t.datetime "updated_at",   null: false
  end

どのように最新のメッセージを一件取得するばいいかアドバイス頂けると嬉しいです。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

+1

もうベストアンサーついてますが、こんなやり方もあるよという意味で回答します。

@messages.order(created_at: :desc).take

また、find_byとorderを組み合わせて使用することで、1件のみ取得することも可能です。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/03/29 14:56

    ご回答ありがとうございます。
    サブクエリを色々調べていたのですが、うまく記述できず困っていたので、KaiShoyaさんの回答を参考にこちらも試してみます!

    キャンセル

  • 2017/03/29 15:31

    横からすみません。
    @mesagesに何が入っているのかわかりませんが、この方法だとすべてのmessageのうちの最新のものしか取得できないように見えます。
    質問者さんはLINEのトーク一覧のようなものを作りたいそうなので、各conversationで最新のmessageを取得する必要がありそうに思いました。(間違ってたらすみません...)

    キャンセル

  • 2017/03/29 15:44 編集

    あー、確かにそうですね。
    メッセージ機能は実装済みとのことなので、トーク別でのメッセージ取得はできている前提で話してました。
    conversations/index.html.erbを見直しましたが、こんな感じでしょうか。
    ```
    <% @conversations.each do |conversation| %>
    <%= conversation.messages.order(created_at: :desc).take %>
    <% end %>
    ```

    キャンセル

  • 2017/03/29 17:44

    実現したいのはchromeさんが仰るように「各conversationで最新のmessageを取得する」です。
    私の説明が伝わりづらくてすみません。

    教えて頂いた
    <%= conversation.messages.order(created_at: :desc).take %>
    で試してみたところ、
    #<Message:0x007fdab6718200>
    と表示されてしまい、メッセージの内容(body)を取得できません。
    原因はどこにあるんでしょうか?

    キャンセル

checkベストアンサー

0

サブクエリを使えばいけると思います。

conversations_controller.rbindex内で

@messages = Message.where(%"
        messages.created_at = (
        SELECT MAX(m_sub.created_at)
        FROM messages m_sub
        GROUP BY m_sub.conversation_id
        )")

適当に書いたコードなのでこれそのままは使えないと思いますが、たぶんこんな感じでサブクエリを使えば各conversationでcreated_atが最新のmessage一覧が取得できると思います。

あとはconversations/index.html.erb

<% @conversations.each do |conversation| %>
  <%= @message.find{|m| m.conversation_id == conversation.id}.body %>      
<% end %>

みたいにすればいけそうな気がするのですがどうでしょうか。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/03/29 14:30

    ご回答ありがとうございます。
    サブクエリはまだ使用したことがないので、色々と調べながら試してみます!
    ありがとうございました。

    キャンセル

  • 2017/03/29 14:32

    ご回答ありがとうございます。
    サブクエリは使用したことがなかったので色々と調べながら試してみます。
    ありがとうございました!

    キャンセル

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

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

関連した質問

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

  • Ruby

    7653questions

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

  • Ruby on Rails 4

    2435questions

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