ゴールデンウイーク中もずっとRailsチュートリアルをこなして、約1か月程かかってようやく完走しました。大変だった・・・。
完走後に拡張機能を自身で実装する章があり、5つほどある内の1つ
返信機能を実装してRailsチュートリアルは区切りをつけることにしました。
#返信機能
実装にあたり以下をパクらせてもらいました。
■参考サイト
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
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;
以下は効いてないかも・・・
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' %>
の行がないとスタイルが適用されませんでした。
###ルーター
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({
の行から下が変更箇所です。
###モデル
一意のキーで検索しています。
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
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
https://github.com/GodPhwng/sample_app
汚いコミット履歴がいっぱいあります・・・