フリマアプリを作った回顧記録

# 概要
1週間と2日かけ、`Ruby`と`JavaScript`を使ってフリマアプリの簡易版を作成しました。
自分がつまづいたポイントと、どうやって乗り越えたかの自分用記録です。

# 今回作成したアプリの概要
フリマアプリ

* ユーザー登録ができる
* 商品を出品できる
* 出品した商品の情報を編集・削除できる
* 他ユーザーの商品を購入できる
* 購入の度に住所を記入する
* 支払いはクレジット

`Ruby on Rails 6.0.0`を使用

# データベース設計

**気になったところ**

* 必要なテーブルはユーザー、商品、購入記録、配送先
* 日付を管理するカラムはdate型にする
* 基本的に変更されない静的データはActiveHashを用いる→送る値は整数

<details><summary>完成したDB</summary><div>
## Usersテーブル

| Column                  | Type    | Options                   |
|-------------------------|---------|---------------------------|
| nickname                | string  | null: false               |
| email                   | string  | null: false, unique: true |
| encrypted_password      | string  | null: false               |
| first_name              | string  | null: false               |
| last_name               | string  | null: false               |
| first_name_k            | string  | null: false               |
| last_name_k             | string  | null: false               |
| birth                   | date    | null: false               |

### Association
has_many :items
has_many :order

## Itemテーブル

| Column          | Type       | Options                        |
|-----------------|------------|--------------------------------|
| name            | string     | null: false                    |
| text            | text       | null: false                    |
| category_id     | integer    | null: false                    |
| condition_id    | integer    | null: false                    |
| delivery_fee_id | integer    | null: false                    |
| prefecture_id   | integer    | null: false                    |
| delivery_day_id | integer    | null: false                    |
| price           | integer    | null: false                    |
| user            | references | null: false, foreign_key: true |


### Association
belongs_to :user
belongs_to :category_id
belongs_to :condition_id
belongs_to :delivery_fee_id
belongs_to :prefecture_id
belongs_to :delivery_day_id
has_one :order

## Orderテーブル

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


### Association
belongs_to :user
belongs_to :item
has_one :address

## Addressテーブル

| Column        | Type       | Options                        |
|:--------------|:-----------|:-------------------------------|
| postal_cord   | string     | null: false                    |
| prefecture_id | integer    | null: false                    |
| city          | string     | null: false                    |
| house_number  | string     | null: false                    |
| building      | string     |                                |
| phone_number  | string     | null: false                    |
| buy_record    | references | null: false, foreign_key: true |


### Association
belongs_to :order
belongs_to :prefecture_id

<strong>ED図</strong>
![スクリーンショット 2020-12-16 15.05.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956215/ca531ff0-9e53-6995-ee51-717d08e11a7d.png)

</div></details>

# ユーザー管理機能
**大まかな流れ**

* `gem 'devise'`を導入
* 配布されたビューファイルを配置
* DB設計に合わせてフォームのカラムを修正
* `application_controller.rb`で、`email`と`password`以外の値がDBに保存されるようパラメーターを指定

**つまずいた所**

* `password`に半角英数字両方必須のバリデーションをかける(今まで`email`と`password`のバリデーションはいじったことがなかった)
* `email`に`@`が必須のバリデーションをかける
* `email`に一意性のバリデーションをかける
* 漢字の名前とフリガナのダミーデータを生成する
* 生年月日のダミーデータを生成する

**解決法**

<details><summary>`password`のバリデーション設定</summary><div>

このフリマアプリを作成するまで、`devise`の`email`と`password`のバリデーションはほとんどいじったことが無かったので狼狽えました。デフォルトだと、`password`は数字だけでも登録できてしまうみたいです。`devise`に備わっているバリデーションを丸ごと外してしまうので、`email`も自分でバリデーションをかける必要がありました。

以下は、①`devise`のデフォのバリデーションを外す②`password`に英数字必須のバリデーションをかける正規表現③複数のバリデーションをかけるのに便利な`with_options`④２度の入力を一致させるのに必要なバリデーション、について書いています。

```user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
                                       #↑削除
# 'password'にバリデーションをかける正規表現
# '.freeze'は'PASSEOWD_REGEX'が書き換えられないようにするためのメソッド
  PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]+\z/i.freeze

# 'with_options'で複数のオプションを一度にバリデーションに加えることができる
# 'confirmation'→':password'と':password_confirmationで受け取る値が一致するか
  with_options presence: true, format: { with: PASSWORD_REGEX, message:'には英字と数字の両方を含めて設定してください' }, length: { minimum: 6 }, confirmation: true do
    validates :password
  end
```
<strong>参考サイト</strong>
[【Rails】deviseのバリデーションをカスタマイズ](https://qiita.com/Nosuke0808/items/415a82c2cdd66fc7545c)
[Active Record バリデーション](https://railsguides.jp/active_record_validations.html#confirmation)

</div></details>

<details><summary>`email`のバリデーション設定</summary><div>

`email`が二重に登録されるのを防ぐためのバリデーションです。
`uniqueness`が該当します。`case_sensitive: false`は大文字・小文字を区別しません。

```user.rb
# 'uniqueness'→'email'の一意性をバリデーションにかける
# 正規表現で'@'を含めるよう指示
with_options presence: true, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, message: '@を含めて入力してください' }, uniqueness: { case_sensitive: false } do
    validates :email
  end
```
<strong>参考サイト</strong>
[【Rails】メールアドレスのバリデーション](https://qiita.com/aiorange19/items/6f7d1d0f7582b5159b65)
[【Rails】メールアドレスの一意性の検証【Rails Tutorial 6章まとめ】](https://qiita.com/kagamiya9/items/838ef96395842552bcf8)
[instance method Object#freeze](https://docs.ruby-lang.org/ja/latest/method/Object/i/freeze.html)

</div></details>

<details><summary>漢字とフリガナを利用したユーザーのダミーデータを生成する、と生年月日のダミーデータ</summary><div>

これまでダミーデータを作成するには`Faker`しか利用したことが無かったので、「え、漢字の名前？しかもフリガナも？」と戸惑いましたが、検索すると見事にぴったりなライブラリを見つけました。

```spec/factories/users.rb
FactoryBot.define do
  factory :user do
    # 中略
    first_name {Gimei.first.kanji}
    last_name {Gimei.last.kanji}
    first_name_k {Gimei.first.katakana}
    last_name_k {Gimei.last.katakana}
    birth {Faker::Date.between(from: '1990-01-01', to:'2020-12-08')}
  end
end
```
Gimei作ってくれた方本当にありがとうございます・・・。
<strong>参考サイト</strong>
[【Rails】gem ‘gimei’ で日本語のダミーデータを生成する](https://remonote.jp/rails-gem-gimei-japanese)
[【日本語版Faker】「gimei」というGemについて](https://qiita.com/shu1124/items/c092ddcc0cea7c6d72f7)

</div></details>

<details><summary>追加で修正したところ</summary><div>

一旦レビューをお願いして返ってきた修正点です。

```spec/factories/users.rb
factory :user do
  # 中略
  password = Faker::Internet.password(min_length: 6)
```
このままだと、'Faker'が数字だけのパスワードを生成する可能性があり、エラーに引っかかる可能性があるので

```spec/factories/users.rb
password = '1a' + Faker::Internet.password(min_length: 6)
```
に修正しました。</div></details>

# 商品出品機能
**大まかな流れ**

* itemモデル・itemsコントローラーの生成
* `gem 'active_hash'` `gem 'mini_magick'` `gem 'image_processing'`をインストール
* viewsディレクトリのitemsディレクトリに配布されたビューファイルを配置
* カラムを配置
* itemsコントローラーにストロングパラメーター・newアクション・createアクションを定義
* itemモデルにバリデーションを設定

**つまずいた所**

* 画面上で金額を計算させる`JavaScript`のコーディング
* `:price`カラムのバリデーションに値の範囲を設ける

**解決法**

<details><summary>画面上で金額を計算させる</summary><div>

作りたいのがこれ
![1e92f5550e5e4dd2f60f79b3a8b681a0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956215/df878af0-fa86-d54c-f2f8-ac6d3c03f534.gif)

```javascript/card.js
function price() {
  const priceInput = document.getElementById("item-price");

  priceInput.addEventListener("input", () => {
    const inputValue = priceInput.value;
    const addTaxDom = document.getElementById("add-tax-price");
    const profit = document.getElementById("profit");

    addTaxDom.innerHTML = Math.floor(inputValue * 0.1);
    profit.innerHTML = Math.floor(inputValue - inputValue * 0.1);
  })
}

window.addEventListener('load', price)
```

まず`getElementById("item-price")`で金額を入力する要素を獲得し、`priceInput.value`で入力された値を取得しています。この`inputValue`を利用して、`Math.floor`の()内で計算させた値の小数点以下を四捨五入して切り捨ててもらい、画面上には入力した金額の10%の手数料と利益額を出せます。
<strong>参考サイト</strong>
[JavaScriptのMath.floorメソッドの使い方を現役エンジニアが解説【初心者向け】](https://techacademy.jp/magazine/27120)
</div></details>

<details><summary>バリデーションに値の範囲を設定する</summary><div>

ユーザーが設定できる金額は¥300~¥9,999,999なので、この範囲しか入力できないよということをバリデーションに設定しないといけません。

```item.rb
  with_options presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 300, less_than_or_equal_to: 9999999 } do
    validates :price
  end
```
これでバリデーションに値の範囲を設けることができました。
<strong>参考サイト</strong>
[numericalityを使って属性に数値のみが使われている事を検証する
](https://qiita.com/suzy1031/items/c3220a3749aec48d3d43)
</div></details>

# 商品一覧表示・編集・削除機能
ここはサクサク進めました。
DRYの法則が抜けがちなので気をつけます。

# 商品購入機能

ここでは、大きく2つの新しいことに取り組みました。
①オープンAPIの導入
②Formオブジェクトパターン
です。

<details><summary>商品購入機能を実装した</summary><div>

アプリケーションが購入機能を実装する際、ユーザーから直接カードの情報を受け取って決済を処理するのは、事務手続きが多い点、一定のセキュリティー基準を担保するのが難しい点から推奨されません。
そこで、クレジットカード決済代行サービスを介して実装します。今回は`PAY.JP`を使用しました。

処理の流れとしては
①ユーザーがカード入力画面にカード情報を入力
②①をトークン化し、そのトークンをサーバーサイドに送信
します。

また、今回のアプリでは一つのフォームで①トークン②購入情報（購入したユーザーの`id`と購入された商品の`id`記録)③配送先情報の、テーブルが異なる（あるいはない）3つの情報を１度に保存する必要があります。これまでは１フォームに対し１テーブルしか対応してないアプリケーションしか作ったことがありませんでした。ここで使用するのがFormオブジェクトパターンです。
流れとしては
①モデルディレクトリ直下に新しいファイルを作成し、クラスを定義して`ActiveModel::Model`を`inclede`
②`attr_accessor`で、今回のフォームで利用する全ての属性を使えるようにする
③バリデーションの処理を記述
④受け取ったデータの保存の処理について記述
てな感じです。

```model/user_order.rb
class UserOrder
  include ActiveModel::Model
  attr_accessor :token, :postal_code, :prefecture_id, :city, :house_number, :building, :phone_number, :item_id, :user_id

  with_options presence: true do

    validates :token
    
    validates :postal_code, format: { with: /\A\d{3}[-]\d{4}\z/, message: "is invalid. Include hyphen(-)"}
    validates :city, format: { with: /\A[ぁ-んァ-ン一-龥]/, message: "is invalid. Input full-width characters."}
    validates :house_number
    validates :phone_number, format: { with: /\A\d{10,11}\z/, message: "is invalid."}
    validates :user_id
    validates :item_id
  end
  
  validates :prefecture_id, numericality: { other_than: 1, message: "can't be blank" }

  def save
    order = Order.create(user_id: user_id, item_id: item_id)
    Address.create(postal_code: postal_code, prefecture_id: prefecture_id, city: city, house_number: house_number, building: building, phone_number: phone_number, order_id: order.id)
  end
end
```
これで、一つのフォームで複数テーブルのデータを一度に扱えるようになりました。
また`attr_accessor`は、元々(?)モデルに対応するデーブルのカラム名以外の属性を扱いたい場合に使用するので、テーブルにカラムがない`:token`の値も扱えるようになります。これには値の取得を可能にする`ゲッター`と値の更新を可能にする`セッター`を自動的に行ってくれるから、ということなのですが、今度もう少し深ぼってみようと思います。</div></details>

あと途中つまずいたのが、購入した後に`Sold Out`の文字を入れる所です。

<details><summary>購入したかどうかで表示を変える方法</summary><div>

ここは、各商品のデータに「`order`のデータが存在しているか？」という条件分岐をビューファイルに記述します。

```views/order/index.html
<% if item.order.present? %>
  <div class='sold-out'>
    <span>Sold Out!!</span>
  </div>
<% end %>
```
<strong>参考サイト</strong>
[Railsでnil? blank? empty? present?を使いこなそう](https://techracho.bpsinc.jp/baba/2011_11_26/4724)
</div></details>

あとはBasic認証&デプロイして終了です。
正直初めましてのことが多くて戸惑いましたがなんとか基本実装は達成できました。
これからオリジナルを作成するのでさらにゴールが見えなくなりますが頑張ります。
これからは情報整理するためにこまめに書きたいと思います。