LoginSignup
0
1

More than 3 years have passed since last update.

Railsチュートリアル 返信機能を実装

Posted at

ゴールデンウイーク中もずっとRailsチュートリアルをこなして、約1か月程かかってようやく完走しました。大変だった・・・。

完走後に拡張機能を自身で実装する章があり、5つほどある内の1つ
返信機能を実装してRailsチュートリアルは区切りをつけることにしました。

14.4.1 サンプルアプリケーションの機能を拡張する

返信機能

実装にあたり以下をパクらせてもらいました。

■参考サイト
https://stackoverflow.com/questions/57555708/rails-6-how-to-add-jquery-ui-through-webpacker
https://blog.office-aship.info/rails6-webpacker-jquery-ui/

仕様

  • 自分と返信相手と自分をフォローしている人にのみ見える
  • 「@XXXスペース」が入力されたら、返信と判断する ※XXXは一意のユーザー識別キー
  • 一意のユーザー識別キーはユーザーの名前 + _ + ID とする。この時、空白は_に置換する
  • 一意のユーザー識別キーの入力はサジェストで支援する

micropostsテーブルに返信先の一意のユーザー識別キーを保持する列を追加します。列の型はstringにします。

本当はユーザーの名前が変更されてしまった時に何も処理せずにリンクが切れないように
integerにしたかったのですが、そうなるとmicropost登録時に一意のキーからidを求めに行く処理が
where replace(replace((name || '_' || id),' ','_'), ' ', '_') = 'Example_User_1'
のようにDB依存になってしまうので辞めました。(replace関数がないDBがある)

一番スマートにやるには、さらにusersテーブルに一意のキー用の列を設ければ良さそうですが
今回はmicropostsテーブルの拡張だけでどうにかやろうと思います。

「@XXXスペース」で判断する都合上、姓と名の間のスペースは_に置き換えることにします。
この置き換えた値をユーザーに入力させるのは難しいので、サジェストで支援します。

実装

新規列追加

usersテーブルに新規列を追加します。

class AddReplyColumnToMicropost < ActiveRecord::Migration[6.0]
  def change
    add_column :microposts, :in_reply_to, :string
    add_index :microposts, :in_reply_to 
  end
end

jQuery UI導入

yarn add jquery-ui
config/webpack/environment.js
const { environment } = require("@rails/webpacker");

const webpack = require("webpack");
environment.plugins.prepend(
  "Provide",
  new webpack.ProvidePlugin({
    $: "jquery/src/jquery",
    jQuery: "jquery/src/jquery",
    Popper: ['popper.js', 'default']
  })
);

environment.toWebpackConfig().merge({
  resolve: {
    alias: {
      'jquery': 'jquery/src/jquery'
    }
  }
});

module.exports = environment;

以下は効いてないかも・・・

app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("jquery");
require("jquery-ui/ui/widgets/autocomplete");
import "bootstrap";

app/views/layouts/_head.html.erb

<head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
    <%= stylesheet_link_tag '//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/le-frog/jquery-ui.min.css' %>                               
    <%= javascript_pack_tag 'application',
                               'data-turbolinks-track': 'reload' %>
</head>

<%= stylesheet_link_tag '//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/le-frog/jquery-ui.min.css' %>の行がないとスタイルが適用されませんでした。

ルーター

config/routes.rb
Rails.application.routes.draw do

  get 'password_resets/new'
  get 'password_resets/edit'
  get 'sessions/new'
  root 'static_pages#home'
  get  '/help',    to: 'static_pages#help', as: 'help'
  get  '/about',   to: 'static_pages#about'
  get  '/contact', to: 'static_pages#contact'
  get  '/signup',  to: 'users#new'
  get  '/login',   to: 'sessions#new'
  post  '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users do
    member do
      get :following, :followers
    end
    collection do
      get :auto_complete
    end
  end
  resources :users
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :microposts,          only: [:create, :destroy]
  resources :relationships,       only: [:create, :destroy]
end

以下が増えただけですね

    collection do
      get :auto_complete
    end

フォーム

ほぼ丸パクリです。
app/views/shared/_micropost_form.html.erb

<%= form_with(model: @micropost, local: true) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost...", id: "micropost_content" %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="image">
    <%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %>
  </span>
<% end %>

<script type="text/javascript">
  $("[name='micropost[image]']").bind("change", function() {
    var size_in_megabytes = this.files[0].size/1024/1024;
    if (size_in_megabytes > 5) {
      alert("Maximum file size is 5MB. Please choose a smaller file.");
      $("[name='micropost[image]']").val("");
    }
  });
  $('#micropost_content').autocomplete({
      source: "/users/auto_complete.json",
      delay: 300,
      minLength: 2,
      focus: function(event, ui) {
        $("#micropost_content").val(ui.item.value);
        return false;
      },
      select: function(event, ui) {
        $('#micropost_content').val(ui.item.value);
        return false;
      }
    }).data("ui-autocomplete")._renderItem = function(ul, item) {
      return $("<li>").attr("data-value", item.value).data("ui-autocomplete-item", item).append("<a>" + item.caption + "</a>").appendTo(ul);
    };
</script>

<%= f.text_area :content, placeholder: "Compose new micropost...", id: "micropost_content" %>の行と
$('#micropost_content').autocomplete({の行から下が変更箇所です。

モデル

一意のキーで検索しています。

user.rb
    def feed
        following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id"
        Micropost.where("user_id IN (#{following_ids})
                     OR user_id = :user_id OR in_reply_to = :reply_user", {user_id: id, reply_user: unique_user_id })
    end

    def unique_user_id
        "#{name.gsub(/\s/, "_")}_#{id}"
    end
micropost.rb
  before_save   :extract_reply_user



  private
    def extract_reply_user
        if reply_user = content.match(/(@[^\s]+)\s/)
            # 存在チェックはしていない・・・
            self.in_reply_to = reply_user[0][1..-2]
        end     
    end

正規表現で取得します。

コントローラー

app/controllers/users_controller.rb

  def update
    before_unique_user_id = @user.unique_user_id
    if @user.update(user_params)
      # マイクロポストのreplyも更新する
      microposts = Micropost.where(in_reply_to: before_unique_user_id)
      @user.reload
      microposts.update_all(in_reply_to: @user.unique_user_id)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end

もしユーザーの名称が変更されてもリンクが追随されるように、ユーザー更新時はmicropostsのreply列を更新します。

GitHub

汚いコミット履歴がいっぱいあります・・・

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1