Ruby on Rails 자습서 DM 기능 추가

소개



Ruby on Rals 튜토리얼 (5.1 버전) 과제의 "DM 기능"을 작성합니다.
튜토리얼의 계속으로부터 기능을 추가하고 있기 (위해)때문에 User 모델·헬퍼 메소드의 작성등은 실시하고 있지 않으시기 때문에 주의해 주세요.
또한 Action Cable을 사용하지 않고 메시지를 보낼 때마다 모든 메시지를 검색한다는 처리를 실시하고 있습니다.

또한이 기사에서는
Ruby 2.7.2
Rails 5.1
사용하고 있습니다.

만드는 것




users/:id/messages에 DM을 표시하고 메시지 전송 기능도 갖게 합니다.

message 모델 작성, User 모델과의 연관


$ rails g model Message from_id:integer to_id:integer content:text발신자의 id, 수신자의 id로 메시지를 판별하고 있습니다.

db/migrate/유일한 숫자_create_messages.rb
class CreateMessages < ActiveRecord::Migration[5.1]
  def change
    create_table :messages do |t|
      t.integer :from_id
      t.integer :to_id
      t.text :content

      t.timestamps
    end
    add_index :messages, :from_id
    add_index :messages, :to_id
  end
end

from_id,to_id에 index를 붙여 migration을 실시합니다.$ rails db:migrate
app/models/message.rb
class Message < ApplicationRecord
  default_scope -> { order(created_at: :asc) }
  validates :from_id, presence: true
  validates :to_id, presence: true
  validates :content, presence: true, length: { maximum: 300 }
end

메시지 순서, 유효성 검사를 설정합니다.

app/models/user.rb
class User < ApplicationRecord
  has_many :outgoing_messages, class_name: "Message",
                               foreign_key: "from_id",
                               dependent: :destroy
  has_many :incoming_messages, class_name: "Message",
                               foreign_key: "to_id",
                               dependent: :destroy
  .
  .
  .
end

User 모델과의 부착을 실시합니다.

라우팅



config/routes.rb
Rails.application.routes.draw do
  resources :users do
    member do
      resources :messages, only: [:create, :index]
    end
  end
end

다음 라우팅 완료
GET    /users/:id/messages(.:format)           messages#index
POST   /users/:id/messages(.:format)           messages#create

index 액션으로 특정 상대와의 메세지 일람을 렌더링 해, create 액션으로 신규 메세지를 송신+view의 갱신을 실시합니다.

Messages 컨트롤러 처리


$ rails g controller Messages index createMessages 컨트롤러를 작성합니다.

app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  before_action :setup_users

  def index
    @messages = Message.where("from_id IN (:ids) AND to_id IN (:ids)",ids:@ids)
    @message = Message.new
  end


  def create
    @message = current_user.outgoing_messages.build(message_params)
    @message.to_id = params[:id] 
    @message.save
    @messages = Message.where("from_id IN (:ids) AND to_id IN (:ids)",ids:@ids)
    respond_to do |format|
      format.html { redirect_to  messages_urL(@to_user)}
      format.js { render "create"}
    end
  end

  private
  def message_params
    params.require(:message).permit(:content)
  end

  def setup_users
    @to_user = User.find(params[:id])
    @ids = [@to_user.id,current_user.id]
  end
end

index 메소드에서
from 또는 to 컬럼에 자신 또는 상대의 id 를 포함한 message 를 꺼내 인스턴스 변수에 보존하고 있습니다.

create 메소드로 처리
이번에는 Action Cable을 사용하지 않기 때문에 상대방이 송신한 신규 메시지를 취득하기 위해 메시지 송신 실패·성공에 관계없이 메시지를 모두 취득합니다.

또한 current_user 메소드는 rails tutorial에서 작성한 것입니다.

보기



우선은 index 메소드로부터 렌더링 되는 index.html.erb 입니다.

app/views/messages/index.html.erb
<h2><%= @to_user.name %></h2>
<ol id="messages">
    <%= render @messages %>
</ol>
<div id="error_messages">
    <%= render "shared/error_messages", object:@message %>
</div>
<%= render "shared/message_form" %>

app/views/messages/_message.html.erb
<li <%= "class = my-content" if message.from_id == current_user.id %>>
    <%= gravatar_for message.from, size: 50 %>
    <div class="content">
        <span class="message">
            <%= message.content %>
        </span>

        <span class="timestamp">
            <%= message.created_at %>
        </span>
    </div>
</li>

Twitter 등에서 볼 수 있듯이 상대의 메시지와 자신의 메시지의 표시 방법을 바꾸기 위해, 자신의 메시지의 경우는 다른 css를 적용하기 위한 클래스명을 붙이는 처리를 하고 있습니다.

app/views/shared/_message_form.html.erb
<% provide(:button_text, 'Send Message') %>
<%= form_with model: @message do |f| %>
  <%= f.text_area :content, class: 'form-control' %>
  <%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>

app/views/shared/_error_messages.html.erb
<% if object.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(object.errors.count, "error") %>.
    </div>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

다음에 create 메소드로 렌더링 되는 view입니다.

app/views/messages/create.js.erb
$(".form-control").val("");
$("#messages").html("<%= escape_javascript(render(@messages)) %>");
$("#error_messages").html("<%= escape_javascript(render('shared/error_messages', object: @message)) %>");

양식을 지우고, 메시지를 가져오고, 오류 메시지를 표시합니다.

scss



app/assets/stylesheets/custom.scss
// messages
#messages {
  list-style: none;
  padding: 0;

  li {
    padding: 10px 0;
    // border-top: 1px solid #e8e8e8;
    display: flex;
    justify-content: flex-start;
  }

  .content {
    display: inline-block;

    .message {
      padding: 20px;
      display: inline-block;
      background:$gray-medium-light;
      border-radius: 10px;
    }

    .timestamp {
      color: $gray-light;
      display: block;
      font-size: .75em;
    }
  }

  .gravatar {
    margin-right: 10px;
    width: 40px;
    height: 40px;
  }

  li.my-content{
    justify-content: flex-end;
    text-align: right;
    .message {
      background:#71151a;
      border-radius: 10px;
      color: #fff;
    }

    .gravatar{
      display: none;
    }
  }
}

완성








현재 자신이 메시지를 보낼 때만 새로운 메시지를 얻을 수 있기 때문에 Action Cable을 이용하여 실시간 채팅 기능 추가 등을 실시하고 싶습니다.

좋은 웹페이지 즐겨찾기