assign_attributesメソッドとは

Railsを学習していてassign_attributesメソッドを使う必要があり、調べてみたので、忘れないよう議事録として残そうと思いました。

結論

assign_attributesは、複数の属性の値を更新するためのメソッドです。「DBに保存したくないけど値を更新したい」時に使用します。

assign_attributesメソッド

assign_attributesは複数の属性の値を更新するためのメソッドです。
似たようなメソッドにupdatesaveなどがあります。
これらのメソッドとの違いはDB保存の有無です。

assign_attributes 値を更新してDBに保存はしない。
updatesave 値を更新してDBに保存する。

ransackを使い検索機能を実装する

Ransackとは

ransackとは検索機能を実装するためのgemです。
検索に必要な機能を簡単に実装することができます。

検索機能を実装する利点

一覧画面などでは表示している情報が多いため、必要な情報を探すのに時間がかかってしまいます。
そこで検索機能を実装することで、探す手間を省くことができます。

Ransackの実装の流れ

1. ransackのインストール
2. コントローラーの編集
3. 検索フォーム作成
4. 検索結果の表示


1. ransackのインストール

まずはransackを利用するために、Gemfileに以下の行を追加します。

gem 'ransack'

その後bundleコマンドを実行してransack gem をインストールします。

$ bundle install


2. コントローラーの編集

ransackをインストールすると、検索を行うためのransackメソッドが追加されます。
検索をするためにアクションを追加していきます。

 def index
    @q = current_user.tasks.ransack(params[:q])
    @tasks = @q.result(distinct: true)
  end

上記で使われているメソッドを解説していきます。

params[:q]

params[:q]は、この後に作成するビューファイルから送られてくるパラメーターです。検索フォームで入力した値がこのオブジェクト(@q)に渡されています。

ransack

ransackは、送られてきたパラメーターを元にテーブルからデータを検索するメソッドです。 whrereメソッドのransack版です。

result

resultは、ransackメソッドで取得したデータをActiveRecord_Relationのオブジェクトに変換するメソッドです。
whereは実行結果のオブジェクトを返さないので、取得したデータから特定カラムのデータを取得しようとするとエラーになります。
なので、検索結果を変換する必要があります。

distinct: true

distinct: trueは、検索結果が重複することを防いでくれます。
関連する子テーブルの情報を条件に絞り込んで、親テーブルの検索結果を表示するときには必須となります。
検索時に重複したデータを結果として表示するケースは稀なので、癖としてつけておくほうがいいです。


3. 検索フォーム作成

ransackの提供するヘルパーsearch_form_forを使って検索フォームを追加します。
検索フォームは他ページでも使うため、パーシャル化しておくと便利です。

<%= search_form_for q, class: 'mb-3', url: url do |f| %>
  <div class="input-group mb-3">
    <%= f.search_field :title_or_body_cont, class: 'form-control', placeholder: ("検索ワード") %>
    <div class="input-group-append">
      <%= f.submit class: 'btn btn-primary' %>
    </div>
  </div>
<% end %>

search_form_forメソッドを解説していきます。

search_form_for

search_form_forは、ransackで用意されているヘルパーです。
form_forやform_withのransack版です。

_cont

_contは「名称に〇〇を含む」という検索を実現するためのメソッドです。
LIKE検索を行い、検索ワードで入力された文字が含まれるレコードを取得します。
ransackを利用する際は、検索フィールドを一定のルールで命名する必要があります。


f.search_field

f.search_fieldは、検索フォームの設定をするためのメソッドです。
検索条件の設定などを行います。


4. 検索結果の表示

検索機能を実装したいビューファイルで、作成したパーシャルテンプレート(検索フォーム)を呼び出します。

url: url

呼び出し側で指定したパスをローカル変数に渡すことで、汎用性の高いパーシャルが作れます。

<%= render 'search_form', q: @q, url: tasks_path %>


まとめ

  • ransackは検索機能を簡単に実装できるgem。
  • ransackで提供されているメソッドを使えば、検索条件を簡単に設定できる。


参考にしたサイト

【Rails】 ransackを使って検索機能がついたアプリを作ろう! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

find、find_by、whereの違い - Qiita

ページネーション kaminari

kaminariとは?

Railsでページネーションを利用するためによく利用されているgemのことです。


ページネーションとは?

ページネーションとは、レコード件数が一定数を超えた場合に複数のページに分割して表示するようにすることです。
一覧画面ではほとんどの確率で実装されています。
下記の写真に使われているのがページネーションです。 f:id:Mekun:20210714051312p:plain

なぜページネーションが必要なの?

トップページは、画像や文章、関連する情報などを大量に表示していることが多いため、その分ブラウザを表示し終わるまでに時間がかかってしまいます。そうなると、ユーザーにとって使い勝手が悪くなり、ストレスに感じてしまいます。
このような問題を避けるためによく利用される方法がページネーションです。
情報を分割して表示することで、ユーザーにとって使いやすいようにできます。

ページネーション実装の大まかな流れ

  1. kaminariのインストール
  2. コントローラーにページネーションのコードを書く
  3. ページネーションの表示
  4. 表示数の設定
  5. デザインの調整


kaminari gem のインストール

まずはkaminariを利用するために、Gemfileに以下の行を追加します。

gem 'kaminari', '1.1.1'  # rails5系なのでバージョン 1.1.1を使用します。

その後bundleコマンドを実行してkaminari gem をインストールします。

$ bundle install

Gemfile.lockに以下の行が追加されます。

 kaminari (1.1.1)
      activesupport (>= 4.1.0)
      kaminari-actionview (= 1.1.1)
      kaminari-activerecord (= 1.1.1)
      kaminari-core (= 1.1.1)
    kaminari-actionview (1.1.1)
      actionview
      kaminari-core (= 1.1.1)
    kaminari-activerecord (1.1.1)
      activerecord
      kaminari-core (= 1.1.1)
    kaminari-core (1.1.1)


ページネーション定義

これでkaminariが定義しているメソッドを使うことができます。

def index
  @objects = Object.page(params[:page])
end


ページネーション表示

ページネーションを実装させたいviewファイルに下記のコードを記述します。

<%= paginate @objects %>


kaminariの設定

表示件数の設定を行いたいので、設定ファイルを作成します。

$ rails g kaminari:config

上記のコマンドで、config/initializers配下に以下の設定ファイルが作成されます。

# frozen_string_literal: true

Kaminari.configure do |config|
  # config.default_per_page = 25
  # config.max_per_page = nil
  # config.window = 4
  # config.outer_window = 0
  # config.left = 0
  # config.right = 0
  # config.page_method_name = :page
  # config.param_name = :page
  # config.max_pages = nil
  # config.params_on_first_page = false
end

config.default_per_page = 25この部分が表示件数の設定なので25の部分を書き換えると、表示件数を変更することができる。


デザインの調整

kaminariが提供するジェネレータで、テーマ(Thema)と呼ばれるデザインの種類を指定して、好みの形のパーシャルテンプレートを生成できます。
今回はbootstrap4というテーマのパーシャルプレートを生成してみます。 次のコマンドをコンソールで実行します。

$ rails g kaminari:views bootstrap4

実行すると下記のような出力がされ、app/views/kaminari配下にいくつかビューテンプレートファイルが追加されます。

% rails g kaminari:views bootstrap4
Running via Spring preloader in process 43697
      downloading app/views/kaminari/_first_page.html.erb from kaminari_themes...
      create  app/views/kaminari/_first_page.html.erb
      downloading app/views/kaminari/_gap.html.erb from kaminari_themes...
      create  app/views/kaminari/_gap.html.erb
      downloading app/views/kaminari/_last_page.html.erb from kaminari_themes...
      create  app/views/kaminari/_last_page.html.erb
      downloading app/views/kaminari/_next_page.html.erb from kaminari_themes...
      create  app/views/kaminari/_next_page.html.erb
      downloading app/views/kaminari/_page.html.erb from kaminari_themes...
      create  app/views/kaminari/_page.html.erb
      downloading app/views/kaminari/_paginator.html.erb from kaminari_themes...
      create  app/views/kaminari/_paginator.html.erb
      downloading app/views/kaminari/_prev_page.html.erb from kaminari_themes...
      create  app/views/kaminari/_prev_page.html.erb

サーバーを再起動して、http://locallhost:3000にアクセスします。
テンプレートファイルが適用され、ナビゲーション部分の見た目が変更されます。

今回のまとめ

  • kaminariは、ページネーションを簡単に実装できるgem
  • ページネーションは、複数ページに分けて掲載するコンテンツを表示させるようにしたもの
  • ページネーションの見た目を変える場合は、コマンド実行してapp/viewsフォルダに追加されたkaminariを変更する必要がある

参考にしたサイト

【Rails】 kaminariの使い方をマスターしよう! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

Ajax jQuery

今回はコメントの投稿と削除をAjax化する際に、 jQueryを使って実装しました。
しかし、jQuery自体あまり理解していなかったのでまとめていきます。

jQueryとは

jQueryとは、Javascriptのプログラミングでよく行われる定型的な処理を書きやすくしてくれる、オープンソースのライブラリです。 特に、HTMLやCSSの書き換え、イベントの設定やDOM操作や、Ajaxを得意としています。  

DOM操作とは

JavaScriptで行う処理は、大きく分けて「入力」「加工」「出力」の3種類があります。
このうちの「出力」の中で、タグに囲まれたテキストや属性を書き換えたり、HTMLに要素を追加、削除するといった、HTMLやCSSを書き換える処理のことを「DOM操作」いいます。

jQueryの流れ

1. イベントを設定したい要素を取得する
2. 取得した要素にイベントを設定する。

jQueryのプログラムは上記の順序で処理することがほとんどです。
1の処理では、$()メソッドとCSSセレクタを使って簡単にできます。
2の処理では、jQueryのonメソッドを使用します。

1. イベントを設定したい要素を取得する

要素を取得するには$()メソッドを用います。
このメソッドは取得した要素を「jQueryオブジェクト」というjQuery独自のオブジェクトに変換してくれます。
取得した要素に対して、jQueryが提供するメソッドを使用することができます。

2. 取得した要素にイベントを設定する。

取得した要素にイベントをつけるため、jQueryが提供してくれているメソッドを用います。

$("tr#comment-<%= @comment.id %>").remove()

上記のコードは、コメントを消してくれるコードになります。
jQueryのメソッドが下記のページにまとめてあります。
Ajax | jQuery API Documentation

要素にid属性を付与する理由

<tr id="comment-<%= comment.id %>">

上記のようなコードを見て、なぜid属性を付与する必要があるのか気になり調べました。
サーバー側が、DOM要素としてどの要素が選択されて、Ajax通信が発生したのか知ることができないからです。
しかし、サーバーは要素のidを知っています。(user.idやpost.idのこと)
そこで、 DOM要素側にあらかじめid属性を付与し、判別できるようにします。
付与するid属性はHTMLの仕様としてのid属性です。
一般的には、HTMLのid属性に、タスクのidを含む文字列を指定します。

Ajax

今回実装したい機能

Ajaxを用いてブックマークした際、ボタンの切り替えを非同期で行う。

調べたこと

  • Ajaxとは何か?
  • 同期通信、非同期通信の違い
  • Ajaxの実装方法

Ajaxとは何か?

Ajaxとは、Asynchronous JavaScript + XML の略で、非同期通信と呼ばれる通信方法のことを指します。
Ajaxを使用すると画面遷移がなく、データの一部だけ更新することができるので、操作性が良くなります。

同期通信非同期通信の違い

同期通信非同期通信の違いは、リクエストとレスポンスのタイミングが違うことです。
同期通信

  • リクエストからレスポンスの一往復が終わらないと、次のリクエストを出せない。
  • サーバーから受け取った情報をもとにページ全体を1から表示するので時間がかかる。

    非同期通信

  • リクエストからレスポンスの一往復が終わっていなくても、次のリクエストを出せる。

  • レスポンス待ちでもページ全体の処理が止まることなく、別の操作が可能。

    非同期通信の本質は、通信のリクエストとレスポンスのタイミングをずらせるので、
    他の処理を並行して行える
    ことです。
    非同期通信を読み込みなしでページの一部だけ更新できる事と誤解している人が多いですが、これはAjaxの仕組みになります。

Ajaxの実装方法

  1. フォームヘルパーにremote trueを設定する。 2.『〇〇.js.erb』ファイルを作成する。
  2. jsファイルに更新処理を記述する。

1. フォームヘルパーにremote trueを設定する。


今回の例ではlink_toメソッドを例に解説します。

<%= link_to bookmarks_path(board_id: board.id), id: "js-bookmark-button-for-board-#{board.id}", class:"float-right", method: :post, remote: true  do %>
  <%= icon 'far', 'star' %>
<% end %> 

このようにフォームヘルパーにオプションとしてremote: trueをつけてあげることで、フォームを送信する際にHTML形式ではなく、JS形式で送信できるようになります。

2.『〇〇.js.erb』ファイルを作成する。

『〇〇.js.erb』ファイルでは以下の3つのことが可能になります。

1. ファイルに記述したjavascriptのコードを実行できる。
2. ERBタグを使用することができる。
3. インスタンス変数を使用することだできる。
これらの特徴により、画面遷移なしで一部の読み込みだけするような処理を簡単に記述することができます。

3. jsファイルに更新処理を記述する。

$("#js-bookmark-button-for-board-<%= @board.id %>").replaceWith("<%= j(render('boards/bookmark', board: @board)) %>");

上記のようなコードを書くだけで、画面遷移なしでブックマークの解除が実装できます。

Ajax実装のまとめ

  • フォームヘルパーに、remote: trueを記述するとJS形式に変換してくれる。
  • コントローラーの処理が終わると、『〇〇.js.erb』ファイルがレンダリングされる。
  • 『〇〇.js.erb』では、javascriptやERBなどが使え、ページの一部だけを更新する処理を記述することができる。

    参考にしたサイト

【IT用語】 | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

【Rails】 remote: | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

has_many throuth

ユーザーのお気に入りした掲示板を取得したいとき、中間テーブルは外部キーしか保存されていないので、 mapメソッドを使い、変換する作業が必要です。

mapメソッドとは

mapメソッドは、配列の全要素にブロック中の処理で変換を行なった、 新しい配列を作る。

配列.map do |変数|
  変換処理
end

mapメソッドを使うとあまり直接的ではないのと、このコードをビューに落とし込むのも大変になります

そこで、has many throughを使えば、ユーザーのお気に入りした掲示板を直接アソシエーションで取得することができます。
has_many :bookmarks_boards, through: :bookmarks, source: :board

bookmarksメソッド(through:で定義)を実行し、それで得られたBookmarksのインスタンスデータのひとつひとつの要素に対してboardメソッド(source:で定義)を実行する。

バリデーション uniqueness

ブックマーク機能を作る際、unique制約が必要だったので理解を深めるためにまとめます!

なぜunique制約が必要なのか

ブックマーク機能は、どのユーザーが、どの掲示板をブックマークしたという関係性を保存することです。
解除する際は保存したデータを消す必要があり、データが重複していると消したはずなのに同じデータが残ってしまうことがあり、一意性が失われてしまいます。

uniquenessとは

このヘルパーは、オブジェクトが保存される直前に、属性の値が一意(unique)であり重複していないことを検証する。

class Bookmark < ApplicationRecord
  validates :user_id, uniqueness: true
end

上記のコードだと、1ユーザーに対して1つしかブックマークできないようになっています。このままでは作りたい要件とは異なってしまいます。
そこでscopeを使用します。
scopeとはuniquenessに用意されているオプションで、範囲を指定して、一意かどうかをチェックしてくれます。

class Bookmark < ApplicationRecord
 validates :user_id, uniqueness: { scope: :board_id } 
end

上記のコードは、1ユーザーが1掲示板に1ブックマークという範囲を指定しています。

:scopeを用いる一意性バリデーションの違反を防止する目的でデータベース側に制約を作成したい場合は、データベース側で両方のカラムにuniqueインデックスを作成しなければなりません。

class CreateBookmarks < ActiveRecord::Migration[5.2]
  def change
    create_table :bookmarks do |t|
      t.references :user, foreign_key: true
      t.references :board, foreign_key: true

      t.timestamps
    end
    add_index  :bookmarks, [:user_id, :board_id], unique: true
  end
end