U-Yuri’s 健忘録

U-Yuri’s 備忘録

プログラミングを勉強しています。アウトプットに活用しているブログです。

【Rails】既読・未読機能を実装する(自作アプリ:mudazero app)

はじめに

自分で作成しているアプリに、お知らせ機能を追加したが各マイページのお知らせアイコンの横に、未読のものがあればメール画像の上に印(点)をつけ、全て既読しているのであれば印はつけないようにするということをしたい。
※userとnewsについてのモデルは作成済み。

流れ

  1. clickというモデルを作成、user_idとnews_idを外部キーとする。
  2. 各ユーザがニュースをクリックしたら、dbに保存される。
  3. dbがnilだったらdbに保存されて、nilではなかったら保存されない(一番古い情報(クリック時間)のみ保存)。
  4. nilがあればマイページトップページに印がつき、且つ、各ニュース一覧の該当するnilのnewsに「new」がつく。

db作成

まずはクリックしたかどうかのテーブルを作成する。

①今回はclickというテーブルを作成。

$ rails g model click

② userとnewsを外部キーとして設定
③message_show.html.erbに各newsの内容を表示するファイル作成。
wants_controller.rbにまだクリックしたことがないnewsをクリックするとdbに時間とnews_idとuser_idが保存されるコード記述。

def message_show
    @message = News.find(params[:news_id])

    already_clicked = Click.find_by(news_id: params[:news_id], user_id: current_user.id)
    
    if already_clicked.nil?
      Click.create!(
        clicked: params[:created],
        user_id: current_user.id,
        news_id: params[:news_id]
      )
    end
    

    render "message_show"
  end

これで何回クリックしても、古い情報のみ保存され、新しい情報は保存されないコード完成。

お知らせ一覧ページ(news一覧)に未読であれば[new]をつける

wants_controller.rbにクリックの情報がnilかどうか確認のためのインスタンス変数作成。

def message
    @messages = News.all

    @already_clicked = Click.find_by(news_id: params[:news_id], user_id: current_user.id)
    render "message"
  end

②includeを使ってjoinする(newsのテーブルと、clickのテーブルをつなぐ)
where(clicks: {user_id: [current_user,nil]})でclickテーブルの中の、user_id。arrayで[current_user,nil]で、current_userか(or)nilということになる(全ニュースを表示したいのでこのようにした)

def message
    @messages = News.includes(:clicks).where(clicks: {user_id: [current_user,nil]})
    
    render "message"
  end
h1>お知らせ</h1>
<ul>
  <% @messages.each do |message| %>
    <li>
      <% if message.clicks.empty? %>
        <small>new</small>
      <% end %>
      <%= link_to message.title, message_path(message) %>
    </li>
  <% end %>
</ul>

empty?はarrayの結果が[](空)の場合の判断(nilではない)。

これでnewの表示完了。と思ったが、他のユーザがクリックしたnewsが表示されないエラーあり。

もう一度仕切り直し、message.current_user_clickedというふうにcurrent_user_clickedというメソッドの中にcurrent_userが入れられたらいいなぁ。
ということでcurrent_user_clickedというメソッドを作成する。

app/models/news.rbを編集しcurrent_user_clicked`というメソッドを作成する。

class News < ApplicationRecord
  has_many :clicks
  def current_user_clicked(current_user)
    Click.find_by(news_id: id, user_id: current_user.id) #current_userはerbから引数として渡さないと使用できないため引数設定
  end

end

app/views/wants/message.html.erbも編集。

<h1>お知らせ</h1>
<ul>
  <% @messages.each do |message| %>
    <li>
      <% if message.current_user_clicked(current_user).nil? %>
        <small>new</small>
      <% end %>
      <%= link_to message.title, message_path(message) %>
    </li>
  <% end %>
</ul>

これでcurrent_userが読んでいないニュースに関してはnewマークがつくようになった。

トップページのメールアイコンの未読通知印の実装

前回作成したnews.rb内のメソッドを今回も使用。 ①erbの編集

<header>
  <div><h1><span class="logo-jp">むだゼロ</span><br>muda0</h1></div>
  <div class="flex_menu">
    <div class="layer">
      <%= link_to(messages_path) do %>
        <%= image_tag 'mailicon.png' %>
        <% @messages.each do |message|%>
          <% if message.current_user_clicked(current_user).nil? %>
            <p>●</p>
          <% end %>
        <% end %>
      <% end %>
    </div>
    <div><%= link_to "欲しいものを登録", wants_new_path, class: "new_button" %></div>
    <div><%= link_to "ログアウト", destroy_user_session_path, method: :delete %></div>
  </div>
</header>

wants/controller@messages = News.allのみ記述。

【Rails】管理者用画面(お知らせ文編集ページ)を作成 gemなし

はじめに

自身で作成した'mudazero'アプリに、アップデート情報などを通知するお知らせ機能を追加したいので、管理者だけがお知らせを作成できる機能を実装したい。

大まかな流れ

  • Userがadminかどうか判断
  • adminであればお知らせ作成画面を表示することができる。そうでなければエラーになる。
  • お知らせ作成画面は新たに作成。
  • お知らせ文の格納用dbも作成。
  • 各ユーザにお知らせを見せる箇所の作成

手順

① Userテーブルにadmin(管理者)テーブルを追加する(以下の記事参照)。
【Rails】テーブルの追加(sqlite3) - U-Yuri’s 備忘録

rails cから管理者を設定(adminの列にtrueを入れる)。
railsからSQLiteに入れる方法。

irb(main):004> yuri = User.find(2) #管理者にしたい人の情報を変数に入れる。
irb(main):006> yuri.admin = true #adminをtrueにする。(この時点ではまだdbに保存されていないrailsのみ変更された)
irb(main):008> yuri.save #これでdbに保存される

管理者が複数人いる場合や、途中で管理者から外したい場合もこの方法で行う。

②viewの中にadminフォルダを作成し、その中にadmin.html.erbを作成。
% rails g controller adminでコントローラーファイルを作成。 ④管理者(admin=true)の人だけが見れるよう、app/controllers/admin_controller.rbを編集。

class AdminController < ApplicationController
  def admin
    if current_user.admin == true

      render "admin"
    else
      render file: "#{Rails.root}/public/404.html", status: 404
    end

  end
end

⑤お知らせメッセージ(タイトル、メッセージ)のdb作成

$ rails g model news title:string message:text

マイグレーションファイル作成

$ rails db:migrate

あとはdbの各機能の実装。

【Rails】ステータスコードを返す(401を返したい)

renderメソッドのstatusというオプションを使用。

render status: 401

今回の実装

class AdminController < ApplicationController
  def admin
    if current_user.admin = true
      render "admin"
    else
      render file: "#{Rails.root}/public/404.html", status: 401
    end

  end
end

【Rails】テーブルの追加(sqlite3)

はじめに

管理者画面を作成するために、Userテーブルにadminの列を追加したい。

テーブルの追加手順

変更するためのファイルを新しく作成し変更していく形になる。 1. $ rails g migration クラス名 カラム名:データ型でカラム追加や削除のためのファイルを作成する。

  1. 今回はカラムを追加したいので
    % rails generate migration AddAdminToUsers admin:booleanを実行。
    20240429044742_add_admin_to_usersというファイルが作成される。
    boolean = true or faluse の結果が入る時に使用する。

  2. % rails db:migrateでschema.rbに反映される。

create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.boolean "admin"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true

参考: Rails /テーブル作成、カラムの追加や削除[備忘録] #Rails - Qiita

【Rails】外部キー制約の参照制約 (SQLite3::ConstraintException: FOREIGN KEY constraint failed)

はじめに

delete機能を実装したが、デリートしたいテーブルを参照しているテーブルに影響が出るため「SQLite3::ConstraintException: FOREIGN KEY constraint failed」というエラーが出た。
そのため、まずは参照しているテーブルを削除する必要がある。
そこで辿り着いたのが外部キー制約の参照制約というものなので以下に残していく。

delete実装の為の各コード

controller.rb内

def deck_delete
    @deck = Deck.find(params[:id])
    @deck.destroy
 end

index.html.erb内

<ul>
    <% @decks.each do |deck| %>
      <%= form_with url: deck_path(deck), method: :patch do |form|%>
        <%= link_to(deck) do %>
          <li><%= deck.name %></li>
        <% end %>
        <%= form.button "削除", formmethod: :delete, data:{ confirm: "Are you sure?" } %>
      <% end %>
    <% end %>
  </ul>

routes.rb内

delete "deck/:id", to: "pokeca#deck_delete"

手順

マイグレーションファイル内にadd_foreign_key :card_in_decks, :decks, on_delete: :cascadeを追加。

class CreateCardInDecks < ActiveRecord::Migration[7.0]
  def change
    create_table :card_in_decks do |t|
      t.references :deck, foreign_key: true, null: false
      t.references :pokemon, foreign_key: true
      t.references :item, foreign_key: true
      t.references :support, foreign_key: true
      t.references :pokemon_no_item, foreign_key: true
      t.references :stajiamu, foreign_key: true
      t.references :energy, foreign_key: true

      t.timestamps
    end

    add_foreign_key :card_in_decks, :decks, on_delete: :cascade
  end
end

②schema.rbを削除
$ rails db:reset
$ rails db:migrate

以上。

railsの設定もあるらしい→Active Record の関連付け - Railsガイド

カスケード削除の例

Railsで外部キー制約の参照制約の書き方 #Ruby - Qiita

【Rails】formについて(外部キーやらIDやら)

<h2>ポケモン</h2>
<% @pokemons.each do |pokemon|%>
  <%= form_with method: :post, model: @deck do |form| %>
    <input type="hidden" name="pokemon_id" value="<%= pokemon.id%>">
    <%= pokemon.name %>
    <%= form.submit "追加" %>
  <% end %>
<% end %>

IDをひとつづつDBに格納したい。 name=key
value=値になる

一つのフォームにひとつのデータを入れないと、最後の値のみ格納されてしまう。
テキストボックスを表示したくない場合はtype="hidden"とする。