エンジニア転職日記

エンジニア転職に向けての日記です

DM機能の実装(DB設計)

概要

オリジナルアプリの基本的な実装が完了したので、DM機能の追加に入っています。

作成中のアプリについての概要はこちらから。

 

shangang7321.hatenablog.com

 

DB設計

まずは、エンティティの洗い出しから行います。DM機能の実装には大きく分けて3つの要素があります。

・ユーザー管理機能

・チャットルーム機能

・メッセージ管理機能

これらの機能に必要なテーブルは

・usersテーブル

・roomsテーブル

・messagesテーブル

の3つです。各テーブルごとに必要なカラム情報は、以下の通りです。

usersテーブル

・name

・email

・password

roomsテーブル

・name

messagesテーブル

・content

・image

・created_at

・room_id

・user_id

 

次は、テーブル間の関係性を考えていきます。

usersテーブルとmessagesテーブル

1人のユーザーは複数のメッセージを持つ(一対多)

roomsテーブルとmessagesテーブル

1つのチャットルームは複数のメッセージを持つ(一対多)

usersテーブルとroomsテーブル

1人のユーザーは複数のチャットルームに属し、1つのチャットルームは複数のユーザーを持つ(多対多)

多対多の関係性を持つテーブル同士の間には、中間テーブルを用意します。

理由は、ユーザーがルームに属する毎にカラムが増えてしまうことを防ぐためです。

不要なカラムが増えてしまうことはDB設計上良くないこととされています。

 

中間テーブル

中間テーブルは、2つのモデルの関係性を保管するテーブルです。例えば、ユーザー1とユーザー2がルーム1に属しているとすると、中間テーブルには、

・id = 1 user_id = 1 room_id = 1

・id = 2 user_id = 2 room_id = 1

この2つのレコードが保存されます。これにより、どのユーザーとどのユーザーがどのルームに属しているのかがわかります。

 

throughオプション

中間テーブルを使って多対多のアソシエーションを記述する場合は、throughオプションを用います。

例えば、ユーザーが複数のルームに属することを示す場合は、

user.rb

has_many :room_users

has_many :rooms, through: room_users

このように記述します。

 

ここまでの条件を全て考慮してデータベースに表すと、以下のようになります。

 

# テーブル設計

## users テーブル

| Column   | Type   | Options     |
| -------- | ------ | ----------- |
| name     | string | null: false |
| email    | string | null: false |
| password | string | null: false |

### Association

- has_many :room_users
- has_many :rooms, through: room_users
- has_many :messages

## rooms テーブル

| Column | Type   | Options     |
| ------ | ------ | ----------- |
| name   | string | null: false |

### Association

- has_many :room_users
- has_many :users, through: room_users
- has_many :messages

## room_users テーブル

| Column | Type       | Options                        |
| ------ | ---------- | ------------------------------ |
| user   | references | null: false, foreign_key: true |
| room   | references | null: false, foreign_key: true |

### Association

- belongs_to :room
- belongs_to :user

## messages テーブル

| Column  | Type       | Options                        |
| ------- | ---------- | ------------------------------ |
| content | string     |                                |
| user    | references | null: false, foreign_key: true |
| room    | references | null: false, foreign_key: true |

### Association

- belongs_to :room
- belongs_to :user

 

 

参考

https://qiita.com/morikuma709/items/9fde633db9171b36a068

https://programming-beginner-zeroichi.jp/articles/25

 

 

Flexbox まとめ

概要

今回は、サイトの見た目を構築するHTML&CSSの技術についてです。

Flexboxとは

Flexboxは、Flexible Box Layout Moduleの略で、ボックス状の要素を並べるための技術です。

 Flexboxを使うと、これらのメリットがあります。

・要素内の縦横の配置が簡単にできる(コードがシンプル)

・要素間の余白を自動で調節してくれる

・文字の記述量が要素によって異なっても幅を均等にしてくれる

以前は複雑なコードを記述して要素のレイアウトを行っていたようですが、Flexboxを使うと、2〜3行の記述で綺麗にレイアウトできます。

使い方

まずは、横並びに配置する方法です。HTML&CSSでは、画面左上に重力がかかっている積木のような状態なので、初期設定ではどんどん積み重なってしまいます。

Flexboxを使えば、簡単に要素を横並びにできます。

 

displayプロパティ

displayプロパティは、要素の種類(ブロックレベル要素、インライン要素)を変更することがdきるプロパティです。

・display: block; 指定された要素がブロック要素として表示されます。

・display: inline; 指定された要素がインライン要素として表示されます。

・display: inline-block; 指定された要素が、インラインブロック要素として表示されます。

・display: flex; 指定された要素の子要素が横並びになります。

・display: none; 指定された要素が非表示になります。

 

Flexboxを使って要素を横並びにするには、display: flex;を指定します。

そして、その子要素の並び順や要素同士の幅を決める調節方法をFlexboxと呼びます。

 

justify-contentプロパティ

display: flex;を記述した親要素にjustify-content: ◯◯; という形で記述します。

justify-contentプロパティを指定すると、主軸方向(初期値は水平方向)の配置を決めることができます。

・justify-content: flex-start; 左寄せ

・justify-content: flex-end; 右寄せ

・justify-content: center; 中央寄せ

・justify-content: space-between; 両端が左右に寄り余白なし、等間隔

・justify-content: space-around; 両端が左右に寄り等間隔、両端の余白と要素間の余白が1:2

 

主軸方向とは、display: flex;を付与して横並びにした要素が並ぶ方向です。

交差軸方向とは、主軸に直角に交わる方向です。

 

align-itemsプロパティ

display: flex;を付与した親要素に、align-items: ◯◯;という形で記述します。

交差軸方向(初期値は垂直方向)の配置を決められます。

・align-items: flex-start; 上揃え

・align-items: flex-end; 下揃え

・align-items: center; 中央揃え

 

参考

https://www.sejuku.net/blog/56401

https://www.webcreatorbox.com/tech/flexbox

How to ActiveStorage

概要

オリジナルアプリ作成において、画像の投稿機能が必要になったのでここにアウトプットしておきます。

ActiveStorageとは

ActiveStorageは、ファイルアップロードを簡単に実装できるGemです。この機能を使うと、画像の保存、表示、サイズ調整が簡単に行えます。

使い方

事前準備

ActiveStorageで画像をアップロードするには、画像を加工するツール、それをRailsから呼び出すツールをインストールする必要があります。

ImageMagick

ImageMagickは、コマンドラインから簡単に画像の保存形式の変更などが行えるツールです。

image_processing

image_processingは、画像のサイズを調整してくれるGemです。

 

mini_magick

ImageMagickRailsから呼び出すためのツールです。

 

これらをインストールしていきます。

 

まずは、brewからImageMagickをインストールします。

% brew install imagemagick

 

続いて、mini_magick、image_processingをインストールします。

# Gemfile

gem 'mini_magick'
gem 'image_processing', '~> 1.2'

bundle install、サーバー再起動をしたらインストール完了です。

 

次は、ActiveStorageをアプリケーション内で使用できるようにします。

% rails active_storage:install

このコマンドを実行すると、ActiveStorageに関連したマイグレーションファイルが作成されます。マイグレートして情報を反映させましょう。

% rails db:migrate

 

これでActiveStorageが使えるようになりました。

 

保存機能の実装

作成したActiveStorageのテーブルに画像を保存するための実装を行います。

今回は、チャットルームのアプリケーションを例にしているので、作業工程は以下のようになります。

・ActiveStorageのテーブルとMessagesテーブルのアソシエーションを記述する

・messages_controller.rbにてimageカラムの保存を許可する

 

Messagesテーブルのレコードと画像を紐付けるためにhas_one_attachedというメソッドを利用します。has_one_attachedのアソシエーションを記述すると、各レコードと画像ファイルを1対1の関係で紐づけることができます。つまり、1つのメッセージにつき1枚の画像を保存できるということです。

 

messages.rbを開いて以下のように記述します。

class Message < ApplicationRecord
  belongs_to :room
  belongs_to :user
  has_one_attached :image

  validates :content, presence: true
end

 

さらに、messages_controller.rbのストロングパラメーターにてimageカラムの許容を記述します。

  private

  def message_params
    params.require(:message).permit(:content, :image).merge(user_id: current_user.id)
  end

 

表示機能の実装

画像表示部分のhtmlを編集します。

  <div class="lower-message">
      <div class="message-content">
      <!-- 投稿したメッセージ内容を記述する -->
        <%= message.content%>
      </div>
      <%= image_tag message.image, class: 'message-image' %>
  </div>

このように記述すると、画像を表示できるようになります。

しかし、このままでは画像がない場合でも、画像を表示する機能が読み込まれ、エラーが発生してしまいます。エラーハンドリングの記述を行います。

<div class="lower-message">
    <div class="message-content">
    <!-- 投稿したメッセージ内容を記述する -->
      <%= message.content%>
    </div>
    <%= image_tag message.image, class: 'message-image' if message.image.attached? %>
</div>

 

これで画像の表示ができるようになりました。

続いて、表示画像の大きさを調節する機能を実装します。

 

variantメソッド

variantメソッドはActiveStorageを導入している時に使用可能なメソッドです。variantメソッドを使用することで、ビュー側で使用したいサイズをその都度指定できます。

 

画像表示部分のhtmlを再度編集します。

<div class="lower-message">
    <div class="message-content">
    <!-- 投稿したメッセージ内容を記述する -->
      <%= message.content%>
    </div>
    <%= image_tag message.image.variant(resize:'500x500'), class: 'message-image' if message.image.attached?%>
</div>

これで、アップロードした画像が指定した大きさで表示できるようになりました。

 

サービス設計②

概要

 前回の記事で、オリジナルアプリの企画、要件定義が完了しました。今回は引き続きデータベース設計に取り掛かりたいと思います。

shangang7321.hatenablog.com

 

 サービス設計の手順

まず、このアプリはどんな画面があるのか、どのように遷移するのかを考え、まとめていきます。

画面遷移図

画面遷移図とは、どの画面でどんな操作をしたらどのページに移動するかを図に表記したものです。

まずは、アプリで必要な画面を洗い出します。

・サインイン/サインアップページ

・トップページ(メンター一覧を表示)

・メンター詳細ページ

・メンター情報編集ページ

・決済ページ

洗い出した項目をもとに画面遷移図を作成すると以下のようになります。

 

f:id:shangang7321:20200814083811p:plain

 

DB設計

DB設計とは、開発で使用するDB(データベース)の表を設計することです。

具体的に考える項目は、テーブル、カラム、アソシエーションなどです。

設計手順としては、まず保存する項目の洗い出し、それを管理するにはどのようなテーブルがあれば良いかを考え、テーブル同士の関連を考えていきます。

エンティティ

エンティティとは、サービスで扱われるデータ自体のことです。

今回作成するアプリの主な機能は以下の3つです。

・ユーザー管理に関する機能

・メンターのプロフィールに関する機能

・決済に関する機能

エンティティはテーブルと等しい粒度で決める必要があるため、可能なかぎり異なるジャンルの情報は混ぜないようにしましょう。

正規化

正規化とは、データベースの構造を効率的でシンプルな形にすることです。正規化には段階があり、以下の工程を辿ります。

非正規形:何もしていない状態

第一正規形:重複するカラムを分離する

第二、第三正規形:情報が混在するエンティティを分離する

今回のアプリで正規化を行うと、テーブルは以下の3つが必要だということがわかります。

・usersテーブル(ユーザーの情報)

・profileテーブル(メンターの情報)

・ordersテーブル(決済の情報)

制約

制約とは、データを扱う際に制限をかけることです。制約はバリデーションと異なり、データベースに直接設定するので、あとで変更すると手間がかかります。

NOT NULL制約

NOT NULL制約は、テーブルの属性値にNULL(空の値)が入らないように制限する制約です。

マイグレーションファイルのオプションにnull: falseを記述して設定します。

t. :カラム名, null: false

 

一意性制約

一意性制約は、テーブル内で重複するデータを禁止する制約です。emailの保存に使うことが多いです。

主キー制約

主キー制約は、そのデータが空になることがなく、かつ重複していないことを保証する制約です。主キーに対して、NOT NULL制約と一意性制約の両方を設定するのと同義になります。Railsではテーブルを作成する時、主キー制約はidカラムに自動で設定されます。

外部キー制約

外部キー制約とは、外部キーの対応するデータが必ず存在しなくてはならないという制約です。例えば、user_idというカラムには、外部キー制約を付けるべきです。usersテーブルのidが主キーであり、そのidで判別できるレコードと関連付けを行う場合に使用します。オプションに、foreign_key: trueと記述することで設定できます。

t. :カラム名,  foreign_key: true

 

ER図

ER図とは、DBのテーブルなどを図に表記したものです。

表で記述するよりも関連付けがイメージしやすくなります。

今回のアプリをER図に表すと、以下のようになります。

 

f:id:shangang7321:20200814090205p:plain

この図から、以下の関係性が読み取れます。

・1人のユーザーは、1つのプロフィールと多くの決済情報を持つことができる。

・1つのプロフィールは、1人のユーザーに紐付き、多くの決済情報を持つことができる。

・1つの決済情報は、1人のユーザーと1つのプロフィールに紐付く。

 

以上でサービス設計の大枠は完了しました。ここからやっと開発の段階に入ります。

 

 

環境構築メモ

概要

環境構築の際に入力するコマンドや用語についてまとめておきます。

MEMO

ターミナル

ターミナルはPCに命令をすることができるMacのアプリケーションです。

CLI

CLIはCommand Line Interfaceの略で、コマンドを入力してコンピューターに命令する仕組みのことです。ターミナルはCLIの一種です。反対に、画面操作を用いてPCを動かす仕組みをGUI(Graphical User Interface)と呼びます。

シェル

シェルは「ターミナルとOSを繋ぐ窓口役」のような存在です。ターミナルから入力されたコマンドを読み取ってOSに指示を渡し、結果をターミナルに返して表示や実行などの動作をさせます。

シェルにもさまざまな種類があり、bashzshtcshなどが一般的ですが、なかでもzshが2019年よりmacOSの標準シェルとなりました。

zsh

zshはシェルの1つです。ターミナルでecho $SHELLを実行すると現在使用しているシェルがわかります。2019年以前の標準シェルであったbashと比較してもコマンドの実行に大きな違いはありませんが、bashのプロンプトは$なのに対し、zshのプロンプトは%となっています。

zshでは、ドットから始まるドットファイルを作成、編集することで設定を行うことができます。.zshrcの設定を編集してPATHにアプリケーションの場所を示すことで、どのディレクトリからでもコマンドの使用が可能になります。

zshの設定方法は以下のコマンドです。

# zshをデフォルトに設定
% chsh -s /bin/zsh

# ログインシェルを表示
% echo $SHELL
# 以下のように表示されれば成功
/bin/zsh

PATH

PATHとは、環境変数と呼ばれるOS用の変数のことです。コマンドが入力された時に、シェルはPATHにありかを示せば、どこからコマンドを実行してもアプリケーションの場所が検索可能になります。

PATHにアプリケーションのありかを示して、全てのディレクトリからコマンドを使用可能にすることを、一般的に「PATHを通す」と表現します。

例えば、rbenvやMySQLのパスを通すコマンドは以下のようになります。

# rbenvのパスを通す
% echo 'eval "$(rbenv init -)"' >> ~/.zshrc

# MySQLのパスを通す
% echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc

echoコマンドは>>に続けてファイル名を指定することで、ファイルに文字を追加できるコマンドです。.zshrcにrbenvの設定を反映するためのコマンドを記述することで、PATHを通しています。

コマンドラインツール

コマンドで操作するアプリケーションのまとまりを指します。コマンドラインツールを導入することで、OSが初めからコマンドで操作できるアプリケーション以外のアプリケーションをPCにインストールできます。macOS専用のコマンドラインツールにCommand Line Toolsというものがあります。macOSには元々Linuxコマンドで操作できるアプリケーションや機能を標準搭載していますが、Linuxコマンド以外で操作するアプリケーションの多くはCommand Line Toolsを導入することによってまとめてPCに導入できます。

Command Line Toolsのインストールは以下のコマンドです。

% xcode-select --install

パッケージ管理

パッケージとは、プログラムや処理をひとまとめにしたもののことです。ライブラリと似た概念ですが、パッケージは複数のライブラリをまとめていることもあります。

パッケージ管理とは、パッケージやパッケージが持つライブラリなどの依存関係を考慮してインストールやバージョンアップを行う管理のことです。1つのパッケージを利用したい場合、そのパッケージと依存関係にあるパッケージも合わせてインストールしてくれます。

GemfileやBundlerによってGemを管理することもパッケージ管理に含まれます。このような管理を自動的に行ってくれるツールのことをパッケージ管理ツール、またはパッケージマネージャーと呼びます。

Homebrew

macOSのパッケージ管理ツールです。macOS上で動作するアプリケーションの多くがHomebrewからインストールできます。

Homebrewのインストールコマンドは以下です。

% ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Homebrewを使うことで、依存関係にあるパッケージを自動的に合わせるインストールが可能になります。

以下によく使うコマンドをまとめておきます

brew -v

Homebrewのバージョンを表示する

brew install

パッケージをインストールする

brew uninstall

パッケージをアンインストールする

brew list

インストールしたパッケージを表示する

brew search

インストール可能なパッケージを表示する

brew update

インストールしたパッケージを最新へ変更する

Node.js

Node.jsは本来ブラウザ上で動くJavaScriptをサーバー再度で動作させる「実行環境」のことです。Node.jsがインストールされるとJavaScriptのパッケージをサーバーサイドで利用することができます。

Yarn

Node.jsを環境上で動作するパッケージを管理する、JavaScriptのパッケージ管理ツールです。WebフレームワークでJavaScriptを使用する際には、新たにNode.jsのパッケージを追加して利用することもあります。

Railsでは、バージョン6系からJavaScriptのパッケージをデフォルトで利用するようになったので、プロジェクトの作成にYarnのインストールが必須となりました。

バージョン管理

変更したバージョンを記録、あるいは外部から保存して、過去のバージョンや最新のバージョンに切り替えたりすることをバージョン管理と呼びます。

バージョン管理をすることで、パッケージとの依存関係の問題を解消したり、変更して問題が発生したプロジェクトを過去の安定したバージョンに切り替えるなどの対応ができます。Gitによってプロジェクトの変更履歴を管理することもバージョン管理に含まれます。Rubyのバージョン管理には、rbenvを使います。

rbenv

rbenvはRubyのバージョン管理ツールです。複数のバージョンのRubyをダウンロードしておいて、使用するRubyのバージョンをディレクトリごとに指定することも可能です。

バージョン管理ツールを使わない場合、Rubyのバージョンは全てPC内で共通となってしまいます。すると、Rubyのバージョンを最新に更新した際に、あるプロジェクトで使用しているRubyのライブラリが使用できなくなってしまうこともあります。

rbenvを使えば、このようなRubyのバージョン変更に伴う依存関係の問題を回避できます。

rbenvの基本的なコマンドをいかにまとめます。

・rbenv -v

rbenvのバージョンを表示する

・rbenv install [バージョン] 

Rubyのバージョンを指定してインストールする

・rbenv uninstall[バージョン]

Rubyのバージョンを指定してアンインストールする

・rbenv versions

インストールされているRubyのバージョンの一覧を表示する

・rbenv global [バージョン]

全てのディレクトリで使用するRubyのバージョンを切り替える

・rbenv local [バージョン]

カレントディレクトリで使用するRubyのバージョンを切り替える

・rbenv rehash

RubyやGemに関するコマンドをバージョン変更後も使用できるようにする

参考

https://employment.en-japan.com/engineerhub/entry/2019/02/19/103000

https://academy.gmocloud.com/keywords/20170324/4010

 

サービス設計①

概要

プログラミングスクールの卒業要件であるフリマアプリの開発が終わったので、今週からオリジナルアプリの制作に取り掛かっています。

今回は、オリジナルアプリのサービス設計についてまとめました。

作成するアプリケーションは、

「小学生の親がプログラミング教育に関してメンターを探せるアプリ」です。

メンターさんと話し合った際に作成したスライドを載せておきます。

f:id:shangang7321:20200905091148p:plain

大まかな流れ

アプリケーション開発は、大きく分けて5つの工程で行います。

企画→要件定義→設計→開発→保守/運用

の順番です。

企画

どんなアプリ(機能)を作るか決める段階のことです。企画では始めに、「誰のどんな問題を解決したいのか」を決めます。これが決まったら、解決手段として新しくアプリを作る、またはすでにあるアプリに機能を追加します。

要件定義

作るものの詳細を決める段階です。ユーザーがどのような機能を使用するのか考え、言語化していきます。

設計

どんなコードを記述するのか、どんなテーブルを用意するのかを決める段階です。エンジニアが実際にどのようなアプリを作っていくのかという手順書や必要な設計図を作ります。企画、要件定義、設計が終わってはじめて、実際にコードを書く段階に入ります。

開発

実際にコードを記述していく段階です。インターネットに接続していない状態でまず開発し、完成したらインターネット上にアップロードします。これをデプロイと言います。デプロイの手順は他の記事でまとめています。

 

shangang7321.hatenablog.com

 

保守/運用

アプリが公開されたあとの段階です。どれくらいの人数を使われるものなのか、どれくらい重要なものなのかによって保守/運用のやり方は変わります。医療や金融などはミスが許されないので、何重にも対策が練られます。

サービス設計の手順

具体的に、今回開発するアプリケーションを例にサービス設計を行っていきます。

企画

企画は、「どのようなアプリケーションを作成するか」を決める段階です。

大切なのは、「誰のどんな問題を解決するか」という視点です。

まずは、「誰の」にフォーカスして企画を考えます。

ペルソナ

ペルソナとは、マーケティング用語で「サービスを使用するユーザー」のことです。年齢層や職業、性別や現在の境遇などまで決めることで、よりリアルにユーザーの目線でサービスを考案できます。

今回のペルソナは以下のように設定しました。

年齢、性別:30代後半、女性
職業:パートタイマー、子育てに専念
家族構成:夫、3歳上の会社員、息子、8歳
子どもの教育について心配しているが、家庭教師を雇う余裕はない。
プログラミングは自分が全く関わっていないので、抵抗感がある。

 このペルソナから読み取れる課題は、下記の2点です。

・プログラミング必修化に不安を覚えている。

・家庭教師を雇うほどの金銭的な余裕は無い。

次は、この課題をどうすれば解決できるのかを考えます。

 

ユーザーストーリー

ユーザーストーリーとは、「ペルソナの課題に対して、どのような機能で解決していくのか」を明確にしたものです。

ペルソナの課題はこの2点でした。

・プログラミング必修化に不安を覚えている。

・家庭教師を雇うほどの金銭的な余裕は無い。

この課題を解決するためには、プログラミング教育に関して、安価で相談できる相手を探せることが必要です。つまり、メンターと親のマッチングプラットフォームを構築することが必要です。機能に落とし込むと、以下のようになります。

・メンター一覧機能がある

・メンターのプロフィールが見られる

・メンターのプロフィール編集機能がある

・決済機能がある

・ログイン機能がある

他にも、DM機能やメール通知機能、サブスクリプション課金機能などある方が便利そうですが、一旦は最低限の機能で実装していきます。

これで、企画の工程はひとまず完了です。

要件定義

要件定義とは、開発者などがアプリケーションの仕様を把握するために、詳細まで言語化することです。ユーザーストーリーの段階では、人によってそれぞれの機能の完成形のイメージが異なります。したがって、開発に関わる人全員で共通認識をもつために、要件の定義をしていく必要があります。

要件定義は、ページごとに書くと見やすくなります。

今回のアプリの要件定義は以下の通りです。

トップページ

【ボタン】
・ログイン、新規登録
・ログアウトボタン
・メンターログイン、新規登録
・メンター詳細へのリンク
【表示】
・メンター一覧(名前、写真、簡単なプロフィール)

ログイン、新規登録

【ボタン】
・送信
【表示】
・メールアドレス、パスワードでログイン
・名前、メールアドレス、パスワードで新規登録

プロフィール作成、編集

【ボタン】
・送信
【表示】
・タイトル
・写真
・自己紹介

メンター詳細

【ボタン】
・決済画面へのリンク
・一覧表示へ戻る
【表示】
・タイトル
・写真
・自己紹介

決済画面

【ボタン】
・決済ボタン
・メンター詳細へ戻るボタン
【表示】
・カード情報入力するフォーム
・契約するメンター、金額を表示

 

以上で要件定義は完了です。次回、設計の段階についてまとめます。

How to ActiveHash

概要

今回は、カテゴリー選択や都道府県選択などのセレクトボックスに用いる、ActiveHashを解説します。

ActiveHashとは

ActiveHashとは、都道府県やカテゴリーなど、基本的に変更されないデータをモデルに記述し、あたかもデータベースに保存されているかのように取り扱うことができるgemです。これらのデータはユーザーや管理者が編集して値を増やすということを後から行わないので、データベースに保存する必要がありません。ActiveHashを用いると、データベースに保存していないのにもかかわらず、ActiveRecordのメソッドを用いることができるようになります。

使い方

事前準備

まずは、アプリケーションの雛形を作成します。

% cd
% cd projects
% rails _6.0.0_ new active_hash_app -d mysql
% cd active_hash_app
% rails db:create

 

Gemfileを編集します。

gem 'active_hash'

bundle installを実行して、事前準備は完了です。

 

モデル作成

Articleモデルを作成します。

% rails g model article

 

Genreモデルを作成します。

% rails g model genre --skip-migration

--skip-migrationをオプションにつけることでマイグレーションファイルの作成をスキップし、テーブルの作成を阻止しています。

 

Genreモデルを記述していきます。

# app/models/genre.rb
class
Genre < ActiveHash::Base
self.data = [ { id: 1, name: '--' }, { id: 2, name: '経済' }, { id: 3, name: '政治' }, { id: 4, name: '地域' }, { id: 5, name: '国際' }, { id: 6, name: 'IT' }, { id: 7, name: 'エンタメ' }, { id: 8, name: 'スポーツ' }, { id: 9, name: 'グルメ' }, { id: 10, name: 'その他' } ] end

ActiveHash::Baseを継承することで、ActiveHashを使えるようになります。Active::Baseでは、ActiveRecordと同様のメソッドが使用できます。

 

Articleモデルのマイグレーションファイルを編集します。

class CreateArticles < ActiveRecord::Migration[6.0]
 def change
   create_table :articles do |t|
     t.string     :title        , null: false
     t.text       :text         , null: false
     t.integer    :genre_id     , null: false
     t.timestamps
   end
 end
end

t.integer :genre_id とすることによって、articleテーブルにジャンルのIDを保存できるようにしています。記述できたら、rails db:migrateを実行します。

 

Articleモデルを編集します。

class Article < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :genre

  #空の投稿を保存できないようにする
  validates :title, :text, :genre, presence: true

  #ジャンルの選択が「--」の時は保存できないようにする
  validates :genre_id, numericality: { other_than: 1 } 
end

belongs_to_active_hashメソッドが、アソシエーションの役割を果たしています。

なお、Genreモデルにはアソシエーションの記述は必要ありません。

 

記事一覧表示機能の実装

ルーティングを設定します。

Rails.application.routes.draw do
  root to: 'articles#index'
  resources :articles
end

 

コントローラーを作成します。

% rails g controller articles index new

 

記事一覧がビューに表示されるよう編集します。

class ArticlesController < ApplicationController
  def index
    @articles = Article.order("created_at DESC")
  end

  def new
  end
end

 

index.html.erbを編集します。

<h1>記事一覧</h1>
<%= link_to "投稿する", new_article_path, class:"post" %>

<% @articles.each do |article| %>
  <div class="article">
    <div class="article-genre">
      <%= article.genre.name %>
    </div>
    <div class="article-title">
      <%= article.title %>
    </div>
    <div class="article-text">
      <%= article.text %>
    </div>
  </div>
<% end %>

CSSを記述します。

h1{
  text-align: center;
}

.post{
  display: block;
  width: 150px;
  padding: 10px;
  border: 1px solid white;
  background-color: orange;
  color: white;
  text-decoration: none;
  text-align: center;
  margin: 0 auto;
  margin-bottom: 20px;
}

.article{
  height:200px;
  background-color:lightgray;
  display: flex;
  align-items: center;
  flex-direction: column;
  border: 1px solid #fff;
  padding: 20px;
}

.article-genre{
  font-size: 16px;
  border: 1px solid;
  padding: 5px;
  background-color: lightblue;
}

.article-title{
  font-size: 20px;
  font-weight: bold;
}

 

投稿機能の実装

コントローラーを編集します。

class ArticlesController < ApplicationController

  def index
    @articles = Article.order("created_at DESC")
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)
    if @article.save
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def article_params
    params.require(:article).permit(:title,:text,:genre_id)
  end

end

 

new.html.erbを編集します。

<%= form_with model: @article, url:articles_path, local: true do |f| %>
  <div class="article-box">
    記事を投稿する
    <%= f.text_field :title, class:"title", placeholder:"タイトル" %>
    <%= f.text_area :text, class:"text", placeholder:"テキスト" %>
    <%= f.collection_select(:genre_id, Genre.all, :id, :name, {}, {class:"genre-select"}) %>
    <%= f.submit "投稿する" ,class:"btn" %>
  </div>
<% end %>

f.collection_selectメソッドを使用することで、データをプルダウン形式で表示することができます。

 

CSSを記述します。

.article-box {
  height:800px;
  background-color: lightgray;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.title {
  height: 30px;
  width: 300px;
  margin: 20px;
}

.text {
  height: 100px;
  width: 300px;
  resize:none;
  margin-bottom: 20px;
}

.genre-select{
  height: 30px;
  width: 250px;
}

.btn {
  width: 200px;
  margin-top: 20px;
  padding: 15px;
  border: 1px solid white;
  background-color: orange;
  color: white;
}

 

ブラウザで確認してみて、ジャンルがプルダウン形式で表示され、ArticlesテーブルにIDが保存されていれば実装完了です。