12月19日のアドベントカレンダーは大野がやります。
毎年盛り上がっているアドベントカレンダーですが、そもそもアドベントってなんだ。
アドベントカレンダー (Advent calendar) は、クリスマスまでの期間に日数を数えるために使用されるカレンダーである。待降節の期間(イエス・キリストの降誕を待ち望む期間)に窓を毎日ひとつずつ開けていくカレンダーである。すべての窓を開け終わるとクリスマスを迎えたことになる。
クリスマスまでの期間に日数を数えるために使用されるカレンダーでしたか。
クリスマス関連で何か作ろうかと思いましたが、発想が貧困な自分はマッチングサイトしか浮かばなかったのでもうこれでいきます。
マッチング機能中心に試すので、それ以外は想像でお願いします。
デザイン
デザインはbootstrapを使って少し整えることとします。
gem 'bootstrap-sass'
gem 'jquery-rails'
デフォルトでgem 'sass-rails', '~> 5.0'
は入っているので、上記をインストール。
//= require jquery
//= require bootstrap-sprockets
.cssを.scssに変更して以下の記述をする。
@import "bootstrap-sprockets";
@import "bootstrap";
これでbootstrapを使っていきます。
デザインがあるとやる気がだいぶ変わってくるので、まあそれっぽい感じにしておきましょう。
ユーザーをフォローする
お互いがそれぞれフォローし合っていたらマッチング成立とします。そのために、まずはユーザーをフォローする機能をつけていこうと思います。
ユーザー管理はdeviseに任せます。
gem 'devise'
bundle install --path vendor/bundle
rails g devise:install
rails g devise:views
rails g devise user
作ったユーザー間で多対多のアソシエーションを実装します。
ユーザー同士の関係性を保つモデルを作りましょう。
rails g model relationship
class CreateRelationships < ActiveRecord::Migration[5.1]
def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :following_id
t.timestamps
end
add_index :relationships, :follower_id
add_index :relationships, :following_id
add_index :relationships, [:follower_id, :following_id], unique: true
end
end
indexも貼っておきます。
アソシエーション。とりあえずフォローすることしか考えません。
has_many :active_relationships,class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
has_many :following, through: :active_relationships
belongs_to :following, class_name: "User"
いいねのFormを作る
いいねボタンを押すと、そのユーザーをフォローする仕様にしたいと思います。
いいねボタンはユーザーのshowページにおきます。
class UsersController < ApplicationController
#省略
def show
@user = User.find(params[:id])
@relationship = Relationship.new
end
end
.text-center
= image_tag @user.avatar, class: "avatar"
.text-center#user-description
= @user.nickname
- if user_signed_in?
#follow_form
- if current_user.following?(@user)
#liked.btn.btn-default いいね済
- else
= render 'follow', {relationship: @relationship}
%br= @user.profile
def following?(other_user)
following.include?(other_user)
end
= form_with model: relationship, remote: true do |f|
%div= hidden_field_tag :following_id, @user.id
= f.submit "いいね", class: "btn btn-primary"
class RelationshipsController < ApplicationController
def create
current_user.active_relationships.create(create_params)
end
private
def create_params
params.permit(:following_id)
end
end
$("#follow_form").html(`<div class="btn btn-default">いいね済</div>`)
ボタン押したら、createアクション動いてrelationshipsテーブルにはフォローフォロワーの関係が書かれます。
これあれですね、マッチングしたらなんか出るといいですね。マッチングしたけど、「いいね済」としか表示されなかったらなんの面白みもないわ。
そういうことなので、マッチングしたらちょっとした演出を入れたいと思います。これは後の方でほんのちょっと書きます。
マッチングしているユーザー一覧を取得する
お互いがそれぞれをフォローし合ったらマッチング成立とします。
さっきまででユーザーをいいね(フォロー)することはできました。今のままではいいねするだけで、そのユーザーとマッチングしているかどうかは分かりません。
現在ログイン中のユーザーとマッチングしているユーザーを取得したいので、そのための記述をします。
ActiveRecordの力で取得する
自分がフォローしている && 自分がフォローされているユーザーを取得します。お互いがフォローしている状態なので、マッチングしていますね。
ユーザーのフォロワーを取得してそのユーザーがフォローしている人と照らし合わせたいので、アソシエーションを追加します。
has_many :passive_relationships, class_name: "Relationship", foreign_key: "following_id", dependent: :destroy
ログイン中のユーザーとマッチングしているユーザーを取得します。
def matchers
follower_ids = passive_relationships.pluck(:follower_id)
active_relationships.eager_load(:following)
.select{|r|follower_ids.include? r.following_id}
.map{|r|r.following}
end
なげえ・・・
これでユーザーは取得できるけど、こんなに書きたくない。
要はお互いがお互いのことをフォローしていればいいんです。あるユーザーのフォローとフォロワーを取得して、重なっている人がマッチング済のユーザー。
これを簡単に書くためにさらにもう少しアソシエーションを追加します。
has_many :followers, through: :passive_relationships, source: :follower
belongs_to :follower, class_name: "User"
追加したらあとはもう重なり合っているユーザーを取得すればいいだけではないでしょうか。
def matchers
following & followers
end
これでいいんじゃない。
ただ、どちらの場合でもこのとき取ったユーザー集団のクラスはArrayになります。
ActiveRecordRelationが良いという場合は、following、followersの考えでこんな感じでできます。
def matchers
User.where(id: passive_relationships.select(:follower_id))
.where(id: active_relationships.select(:following_id))
end
以下のように、SQLでもActiveRecordRelationにできますね。
Sqlで頑張って取得する
User.matching(current_user)
scope :matching, -> user_id { joins("INNER JOIN relationships ON relationships.follower_id = users.id
INNER JOIN relationships AS r ON relationships.following_id = r.follower_id AND r.following_id = relationships.follower_id").where('relationships.following_id = ?', user_id) }
こんな感じで取得すれば、classはActiveRecord_Relation
となります。
こっちのクラスの方がこの先Happyな気がしますね。
マッチング済のユーザーを表示する
どちらの方法でもあとは呼び出すだけで、ユーザーにマッチングしているユーザーが表示されるはず。
def index
@users = current_user.matchers
end
= render 'partial/navbar'
.container
.col-md-3
= render 'partial/verticalnavbar'
.col-md-9
.panel.panel-default
.panel-heading
.text-center
%span{style: "font-size:18px;"} ユーザー一覧
- @users.each do |user|
.col-sm-4.col-md-3
.panel.panel-default
.panel-heading
%h1.panel-title= link_to "#{user.email}", user_path(user)
.panel-body
.thumbnail
= image_tag(user.avatar)
.panel-footer
indexのviewを呼び出してみます。
とりあえず、マッチング機能は終了。
マッチングの際の演出を追加する
さっき後で書くと言っていたやつです。
やっぱりマッチングの際には何か欲しい。何もないなんてつまらなすぎる。
マッチングの際に何か動作を入れる前に、いいねしたユーザーが自分をフォローしているかの判定条件を記述する必要があります。その条件を通過した場合にマッチングの演出をやってやりましょう。
マッチング判定の条件を追加する
いいねボタンを押した際にマッチングしたら演出をしたいです。記述を書くのはRelationshipのインスタンスがcreateされた瞬間です。
User.find(params["following_id"]).following?(current_user)
こんな感じにすれば、いいねしたユーザーが自分をフォローしているかどうか判断できそう。
判定条件を使う
演出をする際に、シンプルにやるなら今のままjs.erbを使っても良さそうですが、やりにくいので普通にjavascriptでAjaxの記述をします。
そのため、ユーザーをフォローするいいねボタンのフォームからremote: true
の記述とcreate.js.erbは消します。
= form_with model: current_user.active_relationships.build, class: 'like_form' do |f|
%div= hidden_field_tag :following_id, @user.id
= f.submit "いいね", class: "btn btn-primary"
$(".like_form").on("submit",function(e){
e.preventDefault();
var formData = new FormData(this);
var url = $(this).attr('action');
$.ajax({
url: url,
type: "POST",
data: formData,
dataType: 'json',
processData: false,
contentType: false
})
.done(function(data){
//省略
js側で、判定結果を持ちたいので送ります。
following_user = User.find(params["following_id"])
@matching = following_user.following?(current_user)
#省略
json.set! :matching, @matching
あとは、js側でtrueかfalseか判断するだけです。trueの場合には盛大な演出をかますと良いでしょう。
クリスマス仕様にしておきました。
画面が両サイドから来るやつと花火みたいなやつはmojsを使っています。
http://mojs.io/
おまけ
チャット機能もつけると、ちょっとそれっぽくなりますね。
違うユーザーでログインして、マッチングしたユーザーに対してメッセージを送っています。
やっていることは主に2つです。
・メッセージ入力→送信ボタンクリック→AjaxでDB保存、表示
・Ajaxでメッセージ取ってきて自動更新