deviseを使わないサインアップ/サインイン機能の実装

TECH::CAMPに通い始めてはや5ヶ月。

卒業までの期間も残すところあと1ヶ月となりました。

 

個人アプリ作成の合間の時間で作成している、就職活動用の個人アプリの作成において、

deviseを使わずにサインアップ・サインイン機能を実装してみようと思いました。

deviseは便利ですが、サインアップ・サインイン機能どういう仕組みで成り立っているのかがいまいちわかっていないため、

自分で実装してみよう!と思い立ちました。

 

参考にさせていただいた記事

https://qiita.com/tmzkysk/items/12c3392dff6da1c87fdf

https://qiita.com/d0ne1s/items/7c4d2be3f53e34a9dec7

 

  

テーブルとモデルを作成する

class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :username, null: false
t.string :email, null: false, unique: true
t.string :password_digest, null: false
t.timestamps
end
end
end

password_digestは、パスワードを暗号化するためのカラム名です。

必ずpasswordではなくpassword_digestとするようにしましょう。

 

次はモデルです。

class User < ApplicationRecord
has_many :projects
before_save {self.email = email.downcase}

validates :username, presence: true
validates :email,
presence: true,
uniqueness: {case_sensitive: false},
format: {with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i}
has_secure_password
end

保存する時にemailを全て小文字に変換します。

has_secure_password と記載(gem 'bcrypt'がインストールされていれば使えるメソッドです)することで、

・viewで:passwordと:password_confirmationという属性が使えるようになり、

 パスワード入力・確認用の入力項目を作れるようになる。

 password_confirmationはテーブルには保存されないため、

 カラムがなくてもエラーは出ません。

・登録したパスワードを「password_digest」カラムに保存する際に

 暗号化されるため、安全性が保たれる。

・authenticateメソッドが使えるようになる。

 ログイン機能の実装の際に、

 ユーザーが正しいパスワードを入力しているのかどうか判定できるメソッドです。

 

 

 サインアップ・サインインページのマークアップとルーティング

f:id:atsukofu:20200806213547p:plain

f:id:atsukofu:20200806213630p:plain

 

 

サインイン時には、emailとパスワードの組み合わせが登録時のものと一致すれば、

サインインができるという仕組みです。

 

ルーティングはこのようにしています。

Rails.application.routes.draw do
resources :users, only: [:new, :create, :destroy]
resources :sessions, only: [:new, :create, :destroy]
end

users: :new, :create → ユーザーの新規登録

users: :destroy  →  ユーザーの削除

sessions: :new, create  →  ログイン機能(セッションのスタート)

sessions: :destroy  →  ログアウト機能(セッションの終了)

 

 

コントローラーの作成

class UsersController < ApplicationController
def new
@user = User.new
end

def create
@user = User.new(user_params)
if @user.save
session[:user_id]=@user.id
session[:user_name]=@user.username
redirect_to root_path
else
render action :new
end
end

private

def user_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
end

 ここでdeviseでは使わない、session[:user_id]や、session[:user_name]が出てきました。

deviseではログインしているユーザーを「current_user」というメソッドで自動的に取得することができますが、

今回はcurrent_userが使えないため、

session[:user_id]などに、@userの持つ情報をそれぞれ代入しています。

(今回はビューで@userを使うので、userではなく@userにしています。)

 

次にサインイン時のコントローラー

SessionsControllerです。

class SessionsController < ApplicationController
def new
end

def create
user = User.find_by(email: session_params[:email])
if user && user.authenticate(session_params[:password])
session[:user_id] = user.id
session[:user_name] = user.username
redirect_to root_path, notice: 'ログインしました'
else
session[:email] = nil
render :new, notice: 'ログインできませんでした'
end
end

def destroy
reset_session
redirect_to new_session_path, notice: 'ログアウトしました'
end

private
def session_params
params.require(:session).permit(:email, :password)
end
end

サインインの仕組みとしては、

まず、入力したemailアドレスに一致するUserをuserに代入します。

そして

・userが存在すること(=入力したemailを持つUserが存在すること)

・userのもつpasswordが入力したpasswordと一致すること

以上の2つを満たすUserを探し当てることができれば、

session[:user_id]とsession[:user_name]に探し当てたuserの情報をそれぞれ代入します。

最後にroot_pathへ移動すれば、ログイン中のユーザー名をsession[:user_name]で表示させ、ログインできていることを確認します。

f:id:atsukofu:20200806224425p:plain

「3番」という名前のユーザーでログインしました。

しっかりユーザー名が表示されています!

 

 

まとめ

コードだけ見るとすごく簡単なのですが、

まず仕組みを理解するところからなので、これだけで1日かかってしまいました。

sessionの仕組みはPHPを触った時に少し学んだため、理解自体はそこまで時間が掛からなかったと思います。

 

一番引っかかったのは、コントローラーで

user.find_by(email: session_params[:email])とすることろです。

emailの値は「params」ではなく「session_params」に入っているのか!

と理解するまでに時間を要しました・・

 

次はサインインしているユーザー以外がアクセスできないようにするなど

バリデーションを加えて行きたいですが、

次回以降でアウトプットして行こうと思います。

 

 

Railsでrubyの基本メソッドを使う

TECH::CAMPで勉強し始めて19週目。

チーム開発にもようやく慣れてきました。

チーム開発を始めた頃はコンフリクトが起きては大騒ぎでしたが、今は落ち着いて対応できるようになりました。

こんな実戦練習ができるのも学校の環境に感謝です。

 

今はメルカリのクローンサイトを作っていて、商品詳細情報をデータベースから引っ張ってこれる機能を実装しています。

 

そこで、コントローラーやビューに色々と基本のメソッドを使用しましたので

使い方を備忘録として書き留めておこうと思います。

 

・開発にはRails5.2.3を使用しています。

・初期データは、カテゴリ機能実装時に覚えたseed.rbを使ってデータベースに登録します。

・ビューはhamlを使用しています。

・コントローラーは、ビューで使う物ためにインスタンス変数に格納するのがほとんどなので、

・今回の記事も変数に格納する形で記載しています。

 

 

 

条件に合う物をn個返す

・データベースの古い物から4つを表示する

@items = Item.first(4)

 

・データベースの新しいものから4つだと、こう

@items = Item.last(4)

 

 

条件に合う物を全て返す

・「brand」カラムに"ABC"が入っている物を全部返す

@brandItems = Item.where(brand: "ABC")

 

 

商品詳細ページにて、現在表示しているアイテムと同じカテゴリのものをランダムに3つ返す(但し現在表示しているアイテムは除く)

@relatedItems =
Item.where(category_id: @item.category_id)
.where.not(id: params[:id]).order("RAND()").limit(3)

 

ちょっと冗長ですね・・もっといい書き方があるかも?

 

アソシエーションを組んでいるテーブルに紐づいているデータ(配列)のうち、最初のデータだけを表示する。

今回のアプリは1つの商品(itemsテーブル)が複数の画像(imagesテーブル)を持っています。

そのうち、商品詳細ページに大きく表示するのは、1枚目の写真だけという設定です。

= image_tag @item.images.first.image_url,class: "item-image-box__main--image"

f:id:atsukofu:20200713223551p:plain

こんな感じです。

 

 

ビューでテンプレートリテラルを使う

これは意外と使ったことがなかった。(というかRailxになった途端にテンプレートリテラルの存在を忘れていた。)

= #{@item.price}"

 

 

ビューでcase文を使う

条件分岐が多くelseだらけになりそうなので、久しぶりにcase文を使いました。

案外見易くて好きかもしれません。

-case @item.postage_type
- when 1
= "未定"
- when 2
= "らくらくFURIMA便"
- when 3
= "ゆうゆうFURIMA便"
- when 4
= "ゆうメール"
- when 5
- when 6
= "普通郵便(定型、定形外)"
- when 7
- when 8
- when 9
= "クリックポスト"
- when 10

 

 

 

前の商品/後ろの商品にページ遷移する

前の商品に移動(前の商品がない場合はリンクは表示されない

 
- if @item.id != @items.first.id
= link_to item_path(@item.id - 1), class:"item-links__btn" do
前の商品

コントローラーはこう

@items = Item.all
 

後の商品に移動の場合は、こうです。

- if @item.id != @items.last.id
= link_to item_path(@item.id + 1), class:"item-links__btn" do
後ろの商品

 

 

まとめ

今回は、Rubyの学習初期に学んだメソッドを思い出しながら実装しました。

学習初期に、「配列の一番前の値を持ってくることになんの意味が・・?!」と思いながら勉強していたことが、

やっと報われた気がします。

あの頃に諦めずにここまでできるようになれて本当によかった。

 

学習初期の方は、メソッドだけを学んで面白くないと思われる方もいるのではないでしょうか。

先にアプリをある程度作れるようになって、必要になった際に改めて基本を勉強するのも一つの手かもしれませんね。

そのほうが、基本の価値がわかるから身につく気がします。

 

 

 

 

カテゴリ機能の実装

TECH::EXPERTで勉強し始めて19週目。

現在、最終カリキュラムであるチーム開発を行っています。

 

最終カリキュラムの課題は、「メルカリ」などを参考にして、

スクール側から与えられた仕様を備えたフリマアプリを作ることです。

 

大変なのは、初めてチーム開発をするため、gitでの管理の段取りがわからず、

masterブランチにmergeするたびにconflictが出るわ出るわの大騒動(笑)

 

開発初期にもgit関連のトラブルを私が起こしてしまい、

チームでなんとか解決を試みた(みなさん、とっても優しくて本当に感謝!!)のもあり、

だんだんgitに耐性がついてきた・・のではないかと思います。

 

現在私が担当しているのは、

[サーバーサイド]category機能の実装

というもの。

 

これはancestryというgemを使用すると、多階層のツリーが作れるらしく、

下記記事と、公式githubを参考にさせていただき、実装しました。

 

https://qiita.com/ATORA1992/items/03eb78e212080072ab9f

↓公式(英語ですが図もあり、わかりやすいです)

https://github.com/stefankroes/ancestry

 

 

 

categoriesテーブルを作る

categoriesテーブルの完成予定は下図の通りですが、

まずはnameカラムのみを加えたテーブルを作成します。

ancestryカラムはあとで加えます。

f:id:atsukofu:20200705210315p:plain

 

下記コマンドを実行↓

rails g model category

 

マイグレーションファイルを編集。

nameカラムを加えます。

class CreateCategories < ActiveRecord::Migration[5.2]
def change
create_table :categories do |t|
t.string :name, null: false
t.timestamps
end
end
end

 

次に、gemをインストールします。

gem "ancestry"

 

$ bundle install  → サーバー再起動を忘れずに。

 

次に、categoriesテーブルに「ancestry」カラムを追加するマイグレーションファイルを作成します。

下記コマンドを実行↓

$ rails g migration add_ancestry_to_categories ancestry:string:index

 

↓こんなマイグレーションファイルが作られます。

class AddAncestryToCategories < ActiveRecord::Migration[5.2]
def change
add_column :categories, :ancestry, :string
add_index :categories, :ancestry
end
end

 

再度 $ rails db:migrate

 

よっしゃ!

f:id:atsukofu:20200705211032p:plain

 

category.rb(モデルファイル)にancestryを記入。

class Category < ApplicationRecord
has_ancestry
end

 

 

挿入データを作成する

ancestryを使った多階層構造は、データベースに入ったデータを使って表現するため、

カテゴリーをデータベースに入れる必要があります。

データベースに最初からデータを入れるのには、db/seeds.rb というファイルを使用します。

今回は、入れるデータの量が多く、親カテゴリー毎にファイルを分けて作成したいので、

dbディレクトリ直下にseedディレクトリを作成し、

例えば、レディースカテゴリ → ladies.rb

というファイル名をつけ、seed.rbに下記のように記載し、引用します。

require './db/seeds/ladies.rb'

 

では、ladiesカテゴリーの子カテゴリ・孫カテゴリを作成していきます。

メルカリはスクレイピング禁止なので、何か良い方法はないかな・・と考えていたのですが、

なかなか見つかりませんので、探す間に全データ入れられそうです。

今回は手打ちで入れちゃいます。

とりあえず、子カテゴリは2つだけで試してみます。

 

# レディースの子カテゴリー配列
ladies_child_array =
[
'トップス',
'ジャケット/アウター'
]

# レディースの孫カテゴリー別配列
ladies_grandchild_array =
[
[ 'Tシャツ/カットソー(半袖/袖なし)',
'Tシャツ/カットソー(七分/長袖)',
'シャツ/ブラウス(半袖/袖なし)',
'シャツ/ブラウス(七分/長袖)',
'ポロシャツ',
'キャミソール',
'タンクトップ',
'ニット/セーター',
'チュニック',
'カーディガン/ボレロ',
'アンサンブル',
'ベスト/ジレ',
'パーカー'
],
[
'ノーカラージャケット',
'Gジャン/デニムジャケット',
'レザージャケット',
'ダウンジャケット',
'ミリタリージャケット',
'ダウンベスト',
'ジャンパー/ブルゾン',
'ポンチョ', 'ロングコート',
'トレンチコート',
'ダッフルコート',
'ピーコート'
]
]

 

子カテゴリは配列、孫カテゴリは配列の配列として作成します。

ここでこの形にする理由ですが、

子カテゴリの配列のインデックス番号と、孫カテゴリの配列のインデックス番号を合わせておくためです。

 

上記で記入したデータをデータベースに入れる命令を書きます。

まず、親カテゴリから。

parent = Category.create(name: 'レディース')

 

次に、子カテゴリですが、ループ処理で実行します。

ladies_child_array.each do |child|
child = parent.children.create(name: child)
end

 

孫カテゴリもループ処理で実行するのですが、上記のループ処理にネストする形で描きます。

ここで、子カテゴリのインデックス番号と孫カテゴリのインデックス番号を合わせておく必要がありますので、

子カテゴリの式をインデックス番号付きの物に書き換えています。

ladies_child_array.each.each_with_index do |child, i|
child = parent.children.create(name: child)
ladies_grandchild_array[i].each do |grandchild|
child.children.create(name: grandchild)
end
end

 

インデックス番号[ i ]の子カテゴリをデータベースに入れる際に、

孫カテゴリの[ i ]番目の塊をそれぞれデータベースに入れる、という処理です。

 

 最後に、下記コマンドを実行します。

$ rails db:seed

 

もしデータ内容に不備があった場合は、

$ rails db:migrate:reset

でseedsの内容を破棄後、修正分を

$ rails db:seed

で入れ直します。

 

 

テーブルの確認

sequel.proでデータベースの内容を確認すると...

f:id:atsukofu:20200705214637p:plain

無事に入っています!!!やったーーー!!!

 

ancestryカラムの見方ですが、

親カテゴリ → NULL

子カテゴリ → 親カテゴリのid

孫カテゴリ → 親カテゴリのid/子カテゴリのid

という形で関係性を見ることができます。

 

 

まとめ

多階層構造はよく見掛けますが、railsだとこうやって作ると簡単に確実に作れるのだと、勉強になりました!!

そして、新しいことができるようになると、

またプログラミングが楽しくなりました!

 

次は、データベースに入れたカテゴリーをビューに表示できるように実装していきたいと思います!

 

 

 

 

 

XAMPPを使った環境構築

TECHCAMPで勉強し始めて16週目。

 

機能20日土曜日から、拠点が近鉄なんば校に移りました。

感想は・・・

「スカイオ、やっぱ良かったな・・・」

でした。

近鉄なんば校は、「THE家賃の高そうな、駅直結の高層ビル」だったので、

「誰しも憧れる職場」のような場所で勉強できる事が、モチベーションにつながったのです。

 

でも、なんばスカイオもいまいちな点がありまして、

クーラー効きすぎ!!なのです。

エレベーター乗り継ぎをする10階なんて、凍えそうなぐらいに寒かった・・

近鉄なんば校のビルはその点クーラーが適温で、

冷え性の私にはもしかしたら近鉄なんばの方が合っているかもしれません。

 

個人アプリ開発もそれなりにデプロイまで行ったので、

PHPを勉強し始めています。

「気づけばプロ並み PHP」というテキストで勉強しています。

PHPRailsと違い、仮想サーバーを自分でダウンロードし、

開発環境を構築する必要があります。

本でも結構丁寧に説明してくれているのですが、

それでも仮想サーバーを使い慣れていないものにはなかなか大変でしたので、

書き留めておきたいと思います。

 

 

 

XAMPPのダウンロード

今回はテキストに従い、

「XAMPP」をダウンロードしました。(ザンプと読むそうです)

 

以前「MAMP」をダウンロードしたのですが、

無料版をダウンロードしたのに、なぜか有料版のお試しになってしまい、

お試し期間が過ぎれば無料版に切り替わるのかと思いきや、

有料版が機能制限付きで継続(金額の発生はない)するというなんだか使いにくい状態になったので、

これを機にXAMPPに乗り換えます。

 

まずはXAMPPのダウンロードページへ。

https://www.apachefriends.org/jp/index.html

 

f:id:atsukofu:20200621215032p:plain

私はmacなので一番右のmac版を選択します。すると新しいウィンドウが開き・・

f:id:atsukofu:20200621215244p:plain

素晴らしい!と褒めてくれます。

ダウンロード始まっていますので、終わったら開きましょう。

 

f:id:atsukofu:20200621215612p:plain

アプリをドラッグアンドドロップして、アプリケーションフォルダに入れ、

ダウンロード完了です。

 

 

XAMPPのスタンバイ

では開いてみましょう。

f:id:atsukofu:20200621221852p:plain

こんな感じのウィンドウが開きます。

まずは、Startボタンを押します。すると右上のランプが赤→黄→緑に変わります。

 

次に。上の方の「Service」タブをクリックします。

するとこんな画面になります。

f:id:atsukofu:20200621222034p:plain

左下の「Start All」を押すと、MySQLApacheの左のランプが緑に変わります。

次は「Network」タブをクリックします。

f:id:atsukofu:20200621222201p:plain

localhost:8080をクリックし、Enableボタンを押します。

するとlocalhost:8080のランプが緑に変わります。

最後に「Volumes」タブです。

f:id:atsukofu:20200621222325p:plain

「Mount」ボタンをクリックします。

これでスタンバイOKです。

ブラウザで、http://192.168.64.2/にアクセスしてみましょう。

f:id:atsukofu:20200621222508p:plain

こんな画面になりましたら、接続成功です。

 

 

PHPファイルを保存しよう

実際にPHPのファイルを保存するのは、こちらのディレクトリです。

f:id:atsukofu:20200621223012p:plain

 

左の赤丸の部分、普段はない場所だと思います。

これが仮想サーバーの場所です。

その中に、「htdocs」というディレクトリがありますので、

その中に「product」「staff」といったディレクトリを作り、

さらにその中にPHPファイルを作成していきます。

「product」ディレクトリの「index.php」にをブラウザで表示するとなると、

http://192.168.64.2/product/index.php

にアクセスすればOKです。

 

 

さいごに

ここに書いたことは、本にはさらっとしか記載がなかったので、

色々と調べたりいじったりしながら結構時間がかかってしまいました。

基本の「き」なのかもしれませんが、ここで詰まったら面白くないので、

この記事を見た誰かの役に立てればと思います。

 

 

 

 

ActiveStorage使ってみた

TECH::CAMPでプログラミングを勉強し始めて、15週目。

いよいよ来週から最終課題か・・

 

今週いっぱいは、個人の勉強に当てるということで

個人アプリをherokuにデプロイしたところ、

なんと、herokuって一定期間しか画像が保存されないんですね・・(投稿画像のこと)

 

ということで外部ストレージを使用しようと、

応用課題で使用したS3を使用することにしたのですが

carrierwaveやらfogやらとの連携がうまくいかない。

 

どうしたもんかと考えていると、

rails5以降は、「Activestorage」という画像アップロード機能が備えられていると言う事!!

一回使ってみよう!

 

 

 

AcriveStorageのインストール

まずは、ある程度アプリができている前提で、ActiveStorageをインストールします。

$ rails active_storage:install
$ rails db:migrate
 

この2つのコマンドを打つ事で、

ActiveStorageによる画像保存用テーブルと、画像と他のモデルを紐づける中間テーブルが出来上がります。

 

他のモデルと紐付け

今回は、投稿(post)モデルと紐付けます。

model/post.rbに

has_one_attached :image

と記載します。

今回は1枚の画像だけですが、

もしいくつも添付する場合は

has_many_attached :image

と言うふうに書きます。

 

ビューの編集

<%= form_for(@post) do |form| %>
<%= form.text_field :tweet, placeholder: "text" %>
<%= form.file_field :image, placeholder: "image" %>
<%= form.submit '投稿する' %>
<% end %>

フォームは通常通り書きます。

 

<% @posts.each do |post|%>
<div class="tweet-wrapper">
<% if post.tweet && post.image.attached? %>
<%= post.tweet %>
<%= image_tag post.image, class:"image"%>
<% elsif post.tweet %>
<%= post.tweet %>
<% elsif post.image.attached? %>
<%= image_tag post.image, class:"image"%>
<% end %>
</div>
<% end %>

投稿一覧ページです。

ポイントは、

<% if post.tweet && post.image.attached? %>

です。「.attached?」と言うふうに記載します。

 

保存先フォルダの変更

producition.rb

config.active_storage.service = :amazon

:trueとなっているのを:amazonに変更しました。

もし開発環境でもS3に保存するなら、

developmentの方も上記と同様に設定してもいいかと思います。

 

storage.yml

test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>

local:
service: Disk
root: <%= Rails.root.join("storage") %>

# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
service: S3
access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
region: ap-northeast-1
bucket: バケット名を書く

test: とlocal: はそのままでOK

amazon: 以下をコメントアウトし、

regionとバケット名を記入します。

 

AWS S3に必要なgemをダウンロードします。

gem 'aws-sdk-s3', require: false

 

 

下記コマンドで、エディタを開きます。

EDITOR=vim rails credentials:edit
iと打ち込むと入力開始。
aws:
 access_key_id: 123 #自分のアクセスキーIDに書き換える
 secret_access_key: 456 #自分のシークレットアクセスキーに書き換える

esc → :wqで終了。
herokuの方にも上記idと、rails masterkey,バケット名、regionを渡せば
S3に保存できました。

非同期通信でいいね!機能を作る

TECH::CAMPで勉強を始めて14週目。

気がつくと、卒業まであと100日を切っております。

早いもんで・・

 

勉強時間に比例して知識が身についているかどうか不安ではあるのですが、

Web上に投稿されているプログラミングに関する記事の理解度に関しては確実に上がっています。

→インプットする力は上がっている

大事なのは、インプットしたものを自分の物にしてアウトプットできるかどうか。

これは数をこなすしかないと思うので、

色んな知識を身につけながらとにかく手を動かして、考えていくしかないかなと!

 

今回は自作アプリにいいね!機能をつけたときの記録。

自分の理想としては、

・非同期通信にしたい

・自分がいいね!したものは赤く塗り潰したい

・いいね!の数を表示したい

ということを掲げて色々調べながら実装しました。

 

✴︎目次✴︎

  

 

ルーティング、Likeモデルを作る

ルーティングは下記の通り。

post(投稿)にネストさせ、どの記事に対するlikeなのかわかるようにしておきます。

機能はいいね!をつける・いいね!を外すという2つのみなので

createとdestroyだけでOK

resources :posts, except: :index do
resources :likes, only: [:create, :destroy]
end

 

Likeモデルを作成し、

バリデーションで、1つの投稿には一人一回しかいいね!できないことにします。

(いいね!をつけたり外したりは可能)

また、postモデルとuserモデルとのアソシエーションをつけておきます。

class Like < ApplicationRecord
belongs_to :post
belongs_to :user
 
validates_uniqueness_of :post_id, scope: :user_id
 
end

 

Likesテーブルでは、

どの投稿に対し、誰がいいね!したのかわかるように

user_idとpost_idを取得します。

テーブルの関係図はこんな感じです。

(commentsテーブルは今回は無視してください)

f:id:atsukofu:20200525213344p:plain

 

これでuser,like,post間の紐付けができました。

 

 

コントローラーとアクションを実装する

先ほどルーティングに記載しておいた

createとdestroyアクションに対して機能を実装します。

likes_controller.rb

class LikesController < ApplicationController
before_action :set_post
 
def create
@like = current_user.likes.new(post_id: @post.id)
@like.save
@likes = Like.where(post_id: @post.id)
end

def destroy
@like = Like.find_by(post_id: @post.id, user_id: current_user.id).destroy
@likes = Like.where(post_id: @post.id)
end

private
def set_post
@post = Post.find(params[:post_id])
end
end

set_postメソッドで、ルーティングの親であるpostのidを@postとして

使えるようにしています。

また、いいね!をつけた時、解除した時両方で

その記事のいいね!の数を取得するようにしています。

 

 

 

ビューの作成

今回はfont-awesomeで枠線だけのハートと、塗り潰しハートを使用します。

枠線だけのハートは、自分がいいね!していない記事に表示されます。

塗り潰しハート(今回はCSSで赤く色をつけました)は、

自分がいいね!している記事に表示されます。

イメージはこちら

・自分が投稿記事に対しいいね!していないとき

 f:id:atsukofu:20200602212019p:plain

・自分が投稿記事に対しいいね!しているとき

 f:id:atsukofu:20200602211911p:plain

 

 

これは投稿詳細ページ中のいいね!部分。↓

<div id="like-<%= @post.id %>">
<%= render 'likes/like', { post: @post} %>
</div>

idを、”like-<%= @post.id %>"とすることで、

投稿のid名がついたid名をつけることができます。

このidは、jsで操作する際に使用します。

 

また、renderを使用し、likeディレクトリの_like.html.erbというファイルを

参照しています。(※部分テンプレートなのかは後でわかります)

次はそちらのファイルをみてみましょう。

 

likes/_like.html.erb

<%if user_signed_in? %>
<% if Like.find_by(user_id: current_user.id, post_id: post.id) %>
<%=link_to post_like_path(post, post.likes),
{method: "delete", remote: true, class: "likes-count"} do %>
<i class="fas fa-heart haert-red"></i>
<%= post.likes.count %>
<% end %>
<% else %>
<%=link_to post_likes_path(post),
{method: "post", remote: true, class: "likes-count"} do %>
<i class="far fa-heart"></i>
<%= post.likes.count %>
<% end %>
<% end %>
<% else %>
<i class="far fa-heart"></i><%= post.likes.count %>
<% end %>

まず、冒頭のif user_signed_in?は、

railsで使える、ユーザーがログインしているかどうか?というのを判定するメソッドです。

今回は、ログインしていれば、いいね!をつけたり外したりできるけども

ログインしていない場合は、

いいね!の数をみるだけしかできないという仕様にしています。

 

また、link_toで呼び出すアクションを指定していますが

自分がいいね!している記事だとdestroyアクション、

まだ自分がいいね!していない記事だとcreateアクションが呼び出されます。

 

link_toタグの中の「remote: true」という記述ですが、

 これを書いておくことで、操作をjavascriptに飛ばせるという仕組みだそうです。

まだ完全に理解はできておりませんが、仕組み自体はそんな感じ。

 

 

jsファイルを記述する

では、jsファイルの記述です。

こちらもlikesディレクトリに保存しています。

今回は、「ファイル名.js.erb」というファイル形式で作っていきます。

このファイル形式、私は初めて見ました。

このファイル形式だと、jsの式の中に、erbタグを使用することで、

Rubyのコードがかけちゃう、ハイブリッド的?!なもののようです。

例えば、コントローラーで定義したインスタンスをerbタグ内に書くことで

このファイル内でも使用することができるのです。

 

ファイル名は、create.js.erbとdestroy.js.erbとし、

両方とも記述内容は同じです。

$("#like-<%= @post.id %>")
.html("<%= j(render partial: 'like', locals: { post: @post }) %>");

(横幅入りきらないので、途中で改行しちゃってますが、1行での記述です。)

 

ここで、先ほど投稿詳細ページに記載した

<div id="like-<%= @post.id %>">

というid名が生きてくるわけです。

上記のjsではid名で操作するビューを取得していますが、

このid名が、先ほどビューに入れた不思議なid名を取得しているのです。

例えば、投稿のidが「3」だったとすると

id名は「like-3」で、jsでは「#like-3」を取得していることになります。

 

取得したidに対し、htmlの書き換えを行います。

html( )メソッドを使用し、カッコ内の部分を非同期で書き換えますよ〜ということです。

カッコ内の部分はというと、先ほど見ていただきました

likes/_like.html.erb のファイルです。

link_toでアクション呼び出し→コントローラーで処理→モデルにてデータを登録

                 ↓

             ビューはjsで部分的に書き換え

という感じですね!!!

 

※ちなみにrenderの前に記載の「j」は特殊文字エスケープするためのメソッドです。

 

 

今回の感想と今後やりたいこと

たくさんのWeb上の記事を拝見しまして、

結構時間がかかって実装したこのいいね!機能ですが、

後からまとめてみると、id名の部分ですとか、なぜ部分テンプレートにするのかとか、

色んな伏線を回収したような気になって、

理解できた時はとてもうれしかったです。

 

今後のいいね!機能に対する目標

ajaxについての知識を固めて、説明できるレベルになりたい

・いいねの数をいいねした時・解除したときだけではなく、

 何秒か起きに非同期で取得したい

 (ツイッターとかリアルタイムでいいね数増えますよね。

  臨場感あっていいですよね。)

 

色んな記事を参考にして得た私の頭の中を公開しましたが、

まだまだ初学者ですので、わかりにくい部分や間違いがあれば、

ご指摘いただけますと嬉しい限りです。

 

長い記事を読んでいただきありがとうございます!

 

 

 

 

Railsでレーダーチャート を書いてみる(DBから値を取得)

TECH::CAMPで勉強し始めてから13週間目。

TECH::CAMP生でいる期間も、そろそろ半分が過ぎようとしています。

 

今日は、今作っているアプリで一番こだわりたい部分を実装しましたので

それを記録します。

 

Railsアプリにレーダーチャート を導入する

 

  1. レーダーチャートとは?
  2.  gemをダウンロード
  3. 変数定義しましょ
  4. js書きましょ
  5. 大きさ変えてみましょ

 

レーダーチャート とは?

レーダーチャート とは、こんなやつです。

 

図形でバランスを見るやつです。2つのデータの比較なんかによく使われます。

私は現在食品会社で開発をしているので、

アンケートで取得した味の評価データをレーダーチャート にしてプレゼン資料に使ったりすることもあり、好きなグラフの一つです。(かっこいいしね。名前も見た目も)

 

今回は、

コーヒーの味の評価をユーザーが記事として登録するアプリですので、

味の評価を5段階でDBに登録し、そのデータをjavascript(Chart.js)に渡して

チャートにしていただきましょうというわけです。

 

gemをダウンロード

まず、Chart.jsのダウンロードから。gemファイルに

gem 'chart-js-rails'

と記載し、

$ bundle install します。(サーバー再起動忘れずに)

次に、application.jsのファイルに

//= require jquery
//= require Chart.min

を記載します。

これでjQueryとChart.jsが使えるようになりました。

 

 変数を定義しましょ

今回はDBの特定のカラムに入っている値を、

モデル→コントローラー→javascript→ビュー

といった順に渡してやる必要があります。

そこで、思いました。

カラムの値を変数でそのままjavascriptに渡しても、読んでくれないんじゃ・・・

 

案の定、そうでした。

jbuilderを使うなど色々試したのですが、なかなかいい方法が思い付かず、

今回はちょっと裏技的で不本意でしたが、

gon というgemを使うことにしました。

gem 'gon'

変な名前やな・・・

 

といってナメてごめんなさい。本当に素晴らしいgemです。

例えば今回渡したいデータは、postモデル内の

acidity」カラム・・・酸味

bitterness」カラム・・・苦味

sweetness」カラム・・・甘味

fragrance」カラム・・・香り

richness」カラム・・・コク

以上の5つだとすると、gonを使用して、下記のようにjavascriptに渡せる変数を作成することができます。

def show
@post = Post.find(params[:id])
gon.acidity = @post.acidity
gon.bitterness = @post.bitterness
gon.sweetness = @post.sweetness
gon.fragrance = @post.fragrance
gon.richiness = @post.richiness
end

 

gon.〇〇というのがjavascriptで使える変数名です。

なんと簡単・・

 

js書きましょ

次はjavascriptでの記述です。

先にソースコードを全部載せます。

$(document).on('turbolinks:load', function(){ //ページを読み込んだらjs発火
var acidity = gon.acidity; //gonを使った変数をjsで変数に代入
var bitterness = gon.bitterness;
var sweetness = gon.sweetness;
var fragrance = gon.fragrance;
var richiness = gon.richiness;
 
var ctx = document.getElementsByClassName("myChart"); //html内のcanvasタグ
(クラスがmyChart)
 
new Chart(ctx, {
type: 'radar', //レーダーチャートを指定
data: {
labels: ["酸味", "苦味", "甘味", "香り", "コク"], //5角形の頂点の名前
datasets: [{
label: '味のバランス', //レーダーチャートの表すもの
backgroundColor: "rgba(0,0,80,0.4)", //グラフの色
borderColor: "rgba(0,0,80,1)", //レーダーの線の色
data: [acidity,bitterness,sweetness,fragrance,richiness] //実際に引っ張るデータの変数
}],
},
options: {
scale: {
pointLabels: {
fontColor: "green" //5角形の頂点の名前の色
},
ticks: {
min: 0, //minimun(チャートの中心)
max: 5, //maximum (五角形の頂点)
stepSize: 1 //1目盛りの区切り
}
}
}
});
});
 
 

こんな感じでかくと、

HTMLの<canvas>タグに、立派なレーダーチャート が簡単に書けちゃいます!!

(クラス名をしっかり指定してやってくださいね)

 

data: [ ] というカッコ内に変数を配列で入れているところには、

数字を直接入れることもできますよ。(ネットで見るとそっちの例が多く出る)

 

そしてできたチャートはこんな感じ。

f:id:atsukofu:20200601221627p:plain

ふむふむ、香りと苦味が少なく、酸味とコク、甘みが少ない・・

焙煎が浅いタイプかしら?!

なんてことが可視化されて便利というわけです。

 

大きさ変えましょ

このチャートを作っている<canvas>タグですが、

そのままクラス名を指定して、大きさをCSSで調整しようとすると、

うまくいきませんでした。

調べてみると、どうやら親要素の大きさに依存するようですので、

divタグで囲ってCSSで大きさ指定してやると、うまくいくみたいです。

これを利用して、レスポンシブデザイン対応にしてみました。(初挑戦)

PC版

f:id:atsukofu:20200601222401p:plain

大きなブラウザ画面に対応。

 

iphoneX版

f:id:atsukofu:20200601222527p:plain小さな画面に対応してグラフの大きさを調整。

 

ちなみに星の数もDBから数値を取得しています。

投稿時に「全体評価」を1〜5の数値で選ばせ、postモデルのstarsカラムに登録。

<% post.stars.times do%>
<span class="top-index__post--stars">
<i class="fas fa-star"></i>
</span>
<% end %>

starsカラムの数字の回数、font-awesomeで取ってきたアイコンの表示を繰り返すだけです。

CSSで色をつけると、サマになりますね!!!

うーん、楽しい・・・!!!

 

 

実装したいUIから作りたいものを探すことが多いです。

今回もその一つです。

ただ、デザインの才能はあまりないので、

なんとなく配色がダサいですね〜

その辺りも色々勉強したいです。