RailsとAjaxでいいね機能実装してみた
Ajaxでいいね機能を実装したので復習かねてメモ
挙動:
画面遷移しないでいいね付ける。
もうちょっと押した感ほしいけど。
js.erbやらjs.hamlやらrender jやらよくわかんなかったからJSテンプレートは使わなかった
参考にしたのはこの記事
Ruby on RailsのAjax処理のおさらい - Qiita
流れとしては
1. いいねボタンクリック
2. createまたはdestroyアクションにajaxでリクエスト
3. createだったらDBに保存、destroyだったらDBから削除
4. jsonレスポンスをフロントに返す
5. 成功(done)したらすでにいいねされてるかされてないかで条件分岐
6. icon-fav-off,icon-fav-offというクラスをremoveしたりaddする
7. レスポンスで帰ってきたlikes_countでいいね総数を表示
モデル
※いいねするユーザー(ここではdevise使って生成されたuser)いいねする対象(ここではarticle)、いいね自体(ここではarticlelike) 3つのモデルがあること 前提。
user.rb
has_many :articlelikes # 1対多 has_many :articles # 1対多
article.rb
belongs_to :user #1対1 has_many :articlelikes, dependent: :destroy #1対多
dependent: :destroy
を指定してあげるとuserがいなくなったとき自動的にいいねも削除してくれる
articlelike.rb
belongs_to :article, counter_cache: :likes_count #1対1 belongs_to :user #1対1
counter_cacheに自分で追加したarticleテーブルのlikes_countカラムを指定してあげると
articlelikeが生成されたり削除されたりするたびにその数を自動的に保持してくれる。
articlelike.findarticleid.count
するより高速
2.ビュー(Haml)
.article-likes.js-article-likes -if user_signed_in? -if current_user.articlelikes.find_by(article_id: article.id) %i.fa.fa-heart.icon-fav-on.js-likes-button{data: {id: article.id}} -else %i.fa.fa-heart.icon-fav-off.js-likes-button{data: {id: article.id}} -else = link_to new_user_session_path do %i.fa.fa-heart.icon-fav-off %span.js-likes-count =article.likes_count
まずユーザーがログインしてるかしてないかで分岐して さらにいいね済みかどうかで分岐 ハートはFont Awesome(
Font Awesome, the iconic font and CSS toolkit
)
iタグにマウスオーバーしたときポインタがリンクになるようにするにはcssでcursor: pointer
を指定
3.コントローラー
def create @article = Article.where(id: params[:article_id]) articlelike = current_user.articlelikes.build(article_id: params[:article_id]) if articlelike.save render json: @article, only:[:likes_count] end end def destroy @article = Article.where(id: params[:article_id]) articlelike = current_user.articlelikes.find_by(article_id: params[:article_id]) if articlelike.destroy render json: @article, only:[:likes_count] end end
4.JS(Coffee)
$-> class FavArticle constructor: ($el) -> # インスタンス作成したときの引数をコンストラクタで受け取ってjqueryobjにして$el変数に格納 @$el = $($el) # いいねボタン探して$likesButton変数に格納 @$likesButton = @$el.find('.js-likes-button') # いいね総数表示要素を取得して$likesCount変数に格納 @$likesCount = @$el.find('.js-likes-count') # イベントリスナー呼び出し @setEventListener() setEventListener: -> # ボタンをクリックした際のイベントリスナー @$likesButton.on 'click', (e) => # Ajax呼び出し @_setLikesAjax(e) _setLikesAjax: (e)-> $this = $(e.currentTarget) # 記事idを取得 articleId = $this.data('id') # destroyアクションへのリクエストURL unLikeURL = '/articleunlike/' + articleId # createアクションへのリクエストURL likeURL = '/articlelike/' + articleId # もしクリックした要素がいいねされてなかったら if $this.hasClass('icon-fav-off') $.ajax({ # createアクションにリクエスト飛ばす url: likeURL # POSTメソッドで type: 'POST' # キャッシュは保持しない cache: false # 記事idを送る data: { 'article_id': articleId } # 帰ってくるデータはjson形式で datatype: 'json' }) # Ajax通信が成功した場合 .done (data) => # 灰色ハートのクラスを削除し赤いハートのクラスを付与 $this.removeClass('icon-fav-off').addClass('icon-fav-on') # いいね総数を表示 @$likesCount.text(data[0].likes_count) else $.ajax({ url: unLikeURL type: 'DELETE' cache: false data: { 'article_id': articleId } datatype: 'json' }) .done (data) => $this.removeClass('icon-fav-on').addClass('icon-fav-off') @$likesCount.text(data[0].likes_count) # いいねボタンといいね総数をwrapしている.js-article-likesをすべて取得 favButtons = document.querySelectorAll('.js-article-likes') # ループ回して一個一個インスタンス生成 for favButton in favButtons new FavArticle(favButton)
こっからajax。最初のdom取得以外全部jquery インターンでCoffee勉強しているからクラス作って書いたけど自信はない。
.js-article-likes
を全部取得してインスタンス生成
コンストラクタでイベトリスナー呼び出し
イベントリスナーで_setLikesAjax()
を呼び出し。
プライベートだから明示的にプレフィックス_をつける
@$likesButton.on 'click', (e) =>
がアロー->
じゃなくてファットアロー=>
の理由
アローだと_setLikesAjax
についてるthis
(coffeeでは@)がjqueryオブジェクト、ここではクリックしたa要素
を指してしまう。
だけどCoffeeだとファットアローにすればコンテクストを維持してくれるので、ここでのthisはインスタンス自身つまりFavArticleになる。
つまり前者でのthis._setLikeAjax
は$(eventObj)._setLikeAjax
になっちゃってエラーが帰ってくる。
後者ではFavArticle._setLikeAjax
と同義になってちゃんと処理が進む。(理解間違ってたらごめんなさい)
_setLikeAjax
のなかではいいねクリックした記事id,いいねURL、いいね削除URLを それぞれ変数に格納
もしクリックした要素が
icon-fav-off
(灰色のハート)クラスを持ってたら
「article likesコントローラーのcreateアクションにpostメソッド、帰ってくる型はjson」でajaxリクエストを出す。
dataは記事のid。
そしてうまくajax通信ができたらrails側からフロントにレスポンスが帰ってくるのでその処理をdone()
の中に書く
処理がうまくいったということはいいねが作成されたということだからこんどはicon-fav-off
をremoveしてicon-fav-on
クラスをadd。
最後に返ってきたデータ(いいねの総数)を表示させておわり
クリックしたいいねボタンがicon-fav-on
(いいね済みの赤いハート)のクラスを持ってたときはその逆
以上
つまったとこ
data-methodが切り替わらない
最初remote: :true
でRailsの力を多大に借りて実装しようと思い実際にかなり最後のほうまで実装したのはよかったけど
最後の最後で、data-method
をattrしても要素の名前が変わるだけで
リクエスト上ではメソッドが切り替わらないという自体が発生してしまって結局やめた。
.data(‘method’, ’NameofMethod')
すればいけるって記事みたけど無理だった
記事のidをどうやってJSに渡すのかわからない
調べたら独自data属性を定義してそこに書いたのをdomで取得すればいいらしいということで今回はそれを実践。data-idという属性に記事idをいれてます。 content_tagを用いる方法もあるっぽい。 ( #324 Passing Data to JavaScript - RailsCasts
) ちなみに独自data属性をどうやってhamlで書くのかわからなくてこれもぐぐった (
haml に HTML5 のカスタムデータ属性(data-*) を定義する方法 - Qiita
)
ちょいちょいhamlでシンタックスエラーでてイラッとする。
ちなみにfontawesomeをlink_toで指定したaタグのなかに埋め込む場合はdoでブロックにする必要性がある
あとはいろいろあったけどもう忘れちゃった
謎なこと
0: Object likes_count: num
???
data.likes_count
で出力させたいのに
data[0].likes_count ってやらないととれない。なぞなぞー
最後に
すごく勉強になった。
でもいろいろ構造的に問題ある気がする。closest('a')とかあんまよくない気がして、
マークアップ構造変わったら終わる笑
処理がかぶってるところも多いからその辺まとめられたらもっといいかな。
あとはエラー処理できてないのが欠点だけどその辺は分岐させたりfailメソッド使えば ちょちょいといけそうな気がスル(なめてる)
次は投票グラフを得票数に応じて動的に変化させるっていうのにチャレンジしたいけどできない気しかしない
そのほか
インターン先のデザイナーさんにアマゾンが開発したダッシュボタン(
アマゾンの「ダッシュ・ボタン」、エイプリル・フールではない - WSJ
)ってサービスを教えてもらって感動した。 WEBとハードを組み合わせたサービスってほんとう素敵。 これこそIoT!(IoT言ってみたかっただけ)