個人アプリ開発 バリデーション・エラーメッセージ設定

コメント機能

文字数制限のバリデーションを追加し、投稿に失敗した場合はアラートを出すようにしました。

f:id:kobegoro:20200829132903p:plain

モデル編集
ーーー追記ーーー
validates :title, presence: true, length: { maximum: 20 }
validates :text, presence: true, length: { maximum: 140 }
コントローラ編集
 def create
    @comment = Comment.create(comment_params)
    if @comment.save
      redirect_to group_path(@comment.group.id)
    else
      ーーー追記ーーー
      flash[:alert] = "コメントの投稿に失敗しました。入力内容をご確認ください。"
      redirect_to group_path(@comment.group.id)
    end
end
ビュー編集
flash.each do |message_type, message|
  .alert
    = message
truncate

ビューを編集している時にコメントのテキストに文字を目一杯入力した場合、見た目が悪くなることに気がつきました。
そしてここで便利なヘルパーメソッドを発見。
いい感じに修正ができました。

f:id:kobegoro:20200829142603p:plain

= truncate(group.comments[-1].text, :length => 90)

90文字を超える文字を"..."に置き換えてくれます

group.comments[-1].text
対象記事の最新コメント(テキスト)を取り出す

アカウント機能

エラーメッセージの日本語化

config/application.rb

module Pictweet
 class Application < Rails::Application
   # Initialize configuration defaults for originally generated Rails version.
   config.load_defaults 6.0


   # 日本語の言語設定
   config.i18n.default_locale = :ja
  # 省略
 end
end

gemインストール

gem ‘rails-i18n'

config/locales/devise.en.ymlに以下URLのコードをコピペ
https://github.com/tigrish/devise-i18n/blob/master/rails/locales/ja.yml

config/locales内にja.ymlを追加し下記を追加

ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        name: 名前
        email: メールアドレス

個人アプリ開発 ランキング順表示

作業内容

記事一覧の表示順を修正しました。
点数が高い順に表示するようにします。

f:id:kobegoro:20200828195832p:plain

コントローラ編集
def index
    @groups = Group.all.sort {|a,b| b.comments.average(:rate).to_f <=> a.comments.average(:rate).to_f}
end

最初はorderメソッドを使って並び替えをしようと思案していましたが、
コードがかなり冗長になり、また、コメントがまだ無い記事は、点数(rateカラム)が存在しない為エラーが出ます。

Qiitaでいろいろ検索してみると、sortメソッドを使用する記事を発見しました。
参考:
[Rails] いいね数順でランキング - Qiita

公式ドキュメントでも該当メソッドを確認し実装。

いろいろ便利なメソッドがあるんだと実感しました。
”定期的に公式ドキュメントを見ておくと良い”という意味が分かりました。精進していきます!

個人アプリ開発 検索機能の実装

作業内容

検索機能の実装を行いました。
フォームに2つの入力欄を持つ、詳細検索機能です(エリアの検索+キーワード検索)。
※キーワード検索は名前・キャッチフレーズ・タイトル・テキストの全てのカラムを検索します

f:id:kobegoro:20200826225632p:plain

gem ransackのインストール

今回はransackを用いました。
gem無しでも実装は可能ですが、単純に興味があったのと、かなりシンプルな記述ができそうだと思った為です。

[Gemfile]
ーーー追記ーーー
gem 'ransack'
[terminal]
$ bundle install
ルーティング設定
resources :groups do
    collection do
      get 'search'
    end
end
コントローラ編集
[groups_controller.rb]
def search
    @search_product = Group.ransack(params[:q]) 
    @groups = @search_product.result
end
[top_controller.rb]
def index
    ーーー追記ーーー
    @q = Group.ransack(params[:q])
end

private

def search_params
    params.require(:q).permit(:area, :text)
end
ビュー編集
.search-form__search-box
      = search_form_for(@q, url: search_groups_path, class: "in-search-form") do |f|
        .search-form__search-box__wrapper
          .search-form__search-box__wrapper__input-area
            = f.text_field :area_cont, id: 'area', class: "search-input", placeholder: 'エリア [ 例:東京 ]'
          .search-form__search-box__wrapper__input-keyword
            = f.text_field :name_or_catchphrase_or_title_or_text_cont, id: 'text', placeholder: 'キーワード [ 例:ペンギン ] '
          .search-form__search-box__wrapper__submit-btn                                                                   
            = f.submit "検  索", class: 'btn'


理解するのに若干時間はかかりましたが、完成してみれば書いたコードの短さに驚き。ransack便利すぎました。
ビューでカラムの検索方法を指定できるのもすごい(あいまい検索や前方一致など)。


参考:https://qiita.com/nakanishi03/items/2fbcc8ba06b336291321


また後日インクリメンタルサーチAjax)機能も付与したいと思います。

個人アプリ開発 いいね機能の実装


いいね機能はチーム開発でも担当した箇所なので、復習がてらサクサク実装しました。
個人アプリでは、”行った”と”保存”の2つの機能があります。

f:id:kobegoro:20200822155436p:plain

記事詳細ページ

モデルの作成
[ターミナル]
$ rails g model Went user:references group:references

$ rails db:migrate
モデルの編集
[group.rb]
has_many :wents, dependent: :destroy

[user.rb]
has_many :wents, dependent: :destroy
ルーティングの編集
resources :groups do
    resources :wents, only: [:create, :destroy]
end
コントローラー作成
[terminal]
$ rails g controller Wents create destroy
コントローラー編集
[wents_controller.rb]
before_action :set_group

  def create
    @went = Went.create(user_id: current_user.id, group_id: params[:group_id])
    @wents = Went.where(group_id: params[:group_id])
    @group.reload
  end

  def destroy
    went = Went.find_by(user_id: current_user.id, group_id: params[:group_id])
    went.destroy
    @wents = Went.where(group_id: params[:group_id])
    @group.reload
  end

  private

  def set_group
    @group = Group.find(params[:group_id])
  end
ビュー作成
[wents/_went.html.haml]
- if Went.find_by(user_id: current_user.id, group_id: group.id)
  = link_to group_went_path(group_id: group.id, id: group.wents[0].id), method: :delete, remote: true do
    .went-btn__wrapper
      %i.fas.fa-flag
      %span
        行った

- else
  = link_to group_wents_path(group), method: :post, remote: true do
    .went-btn__wrapper
      %i.far.fa-flag
      %span
        行った
[groups/show.html.haml]
ーーー追記ーーー
%div{id: "went-#{@group.id}", class: "went-btn"}
    = render "wents/went", group: @group
[wents/create.js.haml]
$("#went-#{@group.id}").html("#{j(render partial: 'went', locals: { group: @group })}");

[wents/destroy.js.haml]
$("#went-#{@group.id}").html("#{j(render partial: 'went', locals: { group: @group })}");

これで”行った”を押すとDBに情報が入るようになりました。

マイページ

ビューの編集
.user_show__main__title
      %h1
        行った水族館
    - if @user.wents.nil?
      %p.went-none
        行った水族館はありません
    - elsif @user.wents.length == 0
      %p.went-none
        行った水族館はありません
    - else
      = render "common/lists"

条件分岐で表示を変えました。

コントローラの編集
[users_controller.rb]
ーーー追記ーーー
@groups = current_user.went_groups

@groupsに値をあげる。
→リスト表示(部分テンプレで繰り返し処理)

f:id:kobegoro:20200822171106p:plain


躓き

同様に”保存”の方も実装を行なっていきましたが、下記エラーが出て上手くいきません。
”uninitialized constant Group::Safe”

”保存”のCreateはできるが、Destroyを行うとエラーが出ます。
→デバックを試みるが問題は無し。
JSのエラーも出ていませんし、パラメータも送られています。
→念のため、タイプミスや記述漏れなど確認しましたが、問題無し。


▶︎エラー内容を再度確認してみました。
Groupの"Safe"の定数が初期化されていませんよ、と怒られています。
Safeに値が入ってない。。。。Safeってなんだ??笑

おそらくSaves(複数形)から自動的にSafe(単数形)を判断し、Safeの値が無いよと教えてくれているよう。
そういえばパスも下記になっていました。
saves#create → group_saves_path
saves#destroy → group_safe_path


命名が悪かったようなので、モデル・コントローラ名をSaveからLikeに変更してみると、OKでした。

そんなことがあるのか。。。と大変勉強になりました笑

これから先エンジニアを続けていく限り、まだ見ぬエラーがたくさん待ち受けているかと思いますが、その都度柔軟に・スピーディーに解決ができるよう精進していきます。

個人アプリ開発 ☆型評価の実装2

引き続き☆型評価の実装を行いました。

☆型評価の表示(コメント欄)

f:id:kobegoro:20200820103513p:plain

ビューの編集
.aquarium-comments__wrapper__comment--bottom
        .aquarium-comments__wrapper__comment--bottom__rate
          %p.rate
            .rate-star{id: "star_rate_#{comment.id}"}

               :javascript
               $('#star_rate_#{comment.id}').raty({
                   size: 36,
                   path: '/assets/',
                   score: #{comment.rate},
                   half: true,
                   readOnly: true,
                });

          %p.number
              = comment.rate
          .aquarium-comments__wrapper__comment--bottom__time
              行った時期:#{comment.visit_date}

readOnly: true,
読み取り専用(入力できない)

score: #{comment.rate},
星の入力値を読み込む

.rate-star{id: "star_rate_#{comment.id}"}
動的にidを振り、対象コメントの評価を表示

javascriptは本当はviewではなく別で管理したいが、一旦わかりやすさ(自分にとっての笑)を重視しました。
後日余裕があればリファクタリングで移動したいと思います。

☆型評価の表示(平均点)

f:id:kobegoro:20200820214802p:plain

ビューの編集
.aquarium__head--left--bottom
          %p.rate
            .rate-star{id: "star_rate_#{@group.id}"}

            :javascript
              $('#star_rate_#{@group.id}').raty({
                size: 36,
                path: '/assets/',
                half: true,
                readOnly: true,
                score: "#{@group.comments.average(:rate).to_f.round(2)}",
              });

            %p.rate-number
              = @group.comments.average(:rate).to_f.round(2)

score: "#{@group.comments.average(:rate).to_f.round(2)}",
averageを使い、記事が持つコメント(点数)の平均点を出す
→平均点を出し、round関数で小数点を切り上げる

個人アプリ開発 ☆型評価の実装

コメント投稿時に評価を入力できるようにしました。

f:id:kobegoro:20200819215834p:plain

作業内容

commentテーブルにrateカラムの追加
$ rails g migration AddRateToComments rate:float

カラムは「0.5」や「1.5」という値を保存することになるため、float型にしておきます。

jQuery Raty

jQueryプラグインjQuery Ratyを使いました。
レーティング(格付け)を星型のアイコンで表示及び入力でき、イメージに近い実装が可能だと思った為です。

Githubのファイル一式をクローン

→対象ファイルをプロジェクトのフォルダに移動
jquery.raty.min.jsをassets/javascriptのフォルダに入れる。
・/lib/img内のイメージをassets/imagesのフォルダに入れる。

application.jsの編集
ーーー追記ーーー
//= require jquery.raty.js
ビューの編集
.comment-form__wrapper
    = form_for [@group, @comment] do |f|
      = f.hidden_field :group_id, { value: @group.id}
      .comment-form__wrapper__main
        #star.comment-form__wrapper__main__rate
          = f.label :rate,'評価 ', class: "rate-label"
          = f.hidden_field :rate, id: :review_star

        :javascript
          $('#star').raty({
            size: 36,
            path: '/assets/',
            scoreName: 'comment[rate]',
            half: true,
          });

        = f.text_field :title, class: 'comment-form__wrapper__title', placeholder: 'タイトルを入れてください'
        = f.text_area :text, class: 'comment-form__wrapper__text', placeholder: '口コミ本文を入れてください'
      .comment-form__wrapper--bottom
        .comment-form__wrapper--bottom--left
          %p
            訪問日
          = f.date_field :visit_date, class: 'comment-form__wrapper__date'
        = f.submit '投稿', class: 'comment-form__wrapper__submit'

:javascript
hamlの中にjsの記述を入れました。

scoreName: 'post[rate]',
Commentモデルのrateカラムに値を保存する

half: true,
★の半分の入力を行う


躓き

表示がされない(JSが効いていない)

application.jsの記述がされていなかった

コンソールを見るとエラーが確認できた為、修正

サイズの指定が悪かった
size: 36px,
→ size: 36,

パスの指定が悪かった
starHalf: 'asset_path('star-half.png')',
→ starHalf: ‘/asset/star-half.png’/,
→ path: ‘/assets/’(最終的にまとめて記述できることがわかった)

rateの値が保存されない

ストロングパラメータで許可していなかった

[comments_controller]
ーーー修正ーーー
def comment_params
    params.require(:comment).permit(:title, :text, :visit_date, :rate).merge(user_id: current_user.id, group_id: params[:group_id])
  end


フォーム内に表示し、DBに保存するまで完了しました。
明日以降、view内の表示(点数結果)を行っていきます。

個人アプリ開発 オーバーレイ・モーダルウィンドウの実装(jQuery)

作業内容

コメント投稿のビューを記事詳細ページのモーダルウィンドウ内に移しました。
オーバーレイ:画面の背景を薄暗くする動作
モーダルウィンドウ:別ウィンドウとして浮かび上がってくる動作
”コメントする”を押すと、コメント投稿フォームが浮かび上がる仕様にします。

f:id:kobegoro:20200816180408p:plain

ビューの編集
[groups/show.html.haml]
ーーー追加ーーー
#overlay
    #modal-window
      .aquarium-comments__form
        %h3 口コミの投稿
        .form
          = form_for [@group, @comment] do |f|
            = f.text_field :title, class: 'form__title', placeholder: 'タイトルを入れてください'
            = f.text_area :text, class: 'form__text', placeholder: '口コミ本文を入れてください'
            = f.date_field :visit_date, class: 'form__date'
            = f.submit '投稿', class: 'form__submit'
        %button#close
          閉じる
CSSの調整

group/show.scssの編集

jQueryの導入
[Gemfile]
# gem 'turbolinks', '~> 5'  ← コメントアウトする
ーーー追加ーーー
gem 'jquery-rails'
$ bundle install
[application.html.haml]
 ~変更前~
!!!
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title giga-app
    = csrf_meta_tags
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' ← このオプションを消す
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' ← このオプションを消す
  %body
    = render "layouts/notifications"
    = yield

 ~変更後~
 省略
    = stylesheet_link_tag    'application', media: 'all'
    = javascript_include_tag 'application'
 省略
[application.js]
//= require jquery      ← 追記
//= require jquery_ujs
//= require turbolinks  ← ここを削除する
//= require_tree .

※//= require_tree .は最下部に。上から順番に読み込む為。
最初記述順を間違え、uncaught referenceerror $ is not definedのエラーが出て、JSファイルが読み込まれませんでした。


turbolinksの機能は下記問題がよく出る為、使わないようにしました。
・$(document).ready()が発火しない
・$(window).load()が発火しない
・metaタグが更新されない
turbolinks;Turbolinksは Asset Pipeline を活用しているアプリケーションにおいて画面遷移を高速化するライブラリ
参考:https://gist.github.com/saboyutaka/8727377

JSファイルの作成
[modal-window.js]
$(function(){
  alert("hello")
$(function() {
  // 「Open」ボタンをクリック時に、fadeInメソッドでHTML要素を表示する
  $('#open').on('click', function() {
    $('#overlay, #modal-window').fadeIn();
  });
  
  // 「Close」ボタンをクリック時に、fadeOutメソッドでHTML要素を非表示にする
  $('#close').on('click', function() {
    $('#overlay, #modal-window').fadeOut();
  });
  
  locateCenter();  // => モーダルウィンドウを中央配置するための初期値を設定する
  $(window).resize(locateCenter);  // => ウィンドウのリサイズに合わせて、モーダルウィンドウの配置を変える

// モーダルウィンドウを中央配置するための配置場所を計算する関数
  function locateCenter() {
    let w = $(window).width();
    let h = $(window).height();
    
    let cw = $('#modal-window').outerWidth();
    let ch = $('#modal-window').outerHeight();
   
    $('#modal-window').css({
      'left': ((w - cw) / 2) + 'px',
      'top': ((h - ch) / 2) + 'px'
    });
  }
});

参考:
JQueryでオーバーレイとモーダルウィンドウを作ってみよう!


躓き

編集ページの作成

コメントの編集箇所に進んだ時に今回の実装では不十分なことに気がつきました。
現状は同じview内にフォームを置いている為、コメントの新規作成と編集を同時に行うことができません。
なので、記事詳細ページとは別のビューとして、コメントの新規作成ページ・編集ページをモーダル表示できるようにするべきでした。
スケジュールを考慮し、一旦編集の際は編集ページに飛ばすようにし、上記は追加実装時の課題としました。

単純に目先の実装方法を考えるだけでなく、一歩引いて全体を見れるように精進していきたいと思います。