概要
1週間と2日かけ、Ruby
とJavaScript
を使ってフリマアプリの簡易版を作成しました。
自分がつまづいたポイントと、どうやって乗り越えたかの自分用記録です。
今回作成したアプリの概要
フリマアプリ
- ユーザー登録ができる
- 商品を出品できる
- 出品した商品の情報を編集・削除できる
- 他ユーザーの商品を購入できる
- 購入の度に住所を記入する
- 支払いはクレジット
Ruby on Rails 6.0.0
を使用
データベース設計
気になったところ
- 必要なテーブルはユーザー、商品、購入記録、配送先
- 日付を管理するカラムはdate型にする
- 基本的に変更されない静的データはActiveHashを用いる→送る値は整数
完成したDB
Column | Type | Options |
---|---|---|
nickname | string | null: false |
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
ユーザー管理機能
大まかな流れ
-
gem 'devise'
を導入 - 配布されたビューファイルを配置
- DB設計に合わせてフォームのカラムを修正
-
application_controller.rb
で、email
とpassword
以外の値がDBに保存されるようパラメーターを指定
つまずいた所
-
password
に半角英数字両方必須のバリデーションをかける(今までemail
とpassword
のバリデーションはいじったことがなかった) -
email
に@
が必須のバリデーションをかける -
email
に一意性のバリデーションをかける - 漢字の名前とフリガナのダミーデータを生成する
- 生年月日のダミーデータを生成する
解決法
`password`のバリデーション設定
このフリマアプリを作成するまで、devise
のemail
とpassword
のバリデーションはほとんどいじったことが無かったので狼狽えました。デフォルトだと、password
は数字だけでも登録できてしまうみたいです。devise
に備わっているバリデーションを丸ごと外してしまうので、email
も自分でバリデーションをかける必要がありました。
以下は、①devise
のデフォのバリデーションを外す②password
に英数字必須のバリデーションをかける正規表現③複数のバリデーションをかけるのに便利なwith_options
④2度の入力を一致させるのに必要なバリデーション、について書いています。
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
`email`のバリデーション設定
email
が二重に登録されるのを防ぐためのバリデーションです。
uniqueness
が該当します。case_sensitive: false
は大文字・小文字を区別しません。
# '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
参考サイト
【Rails】メールアドレスのバリデーション
【Rails】メールアドレスの一意性の検証【Rails Tutorial 6章まとめ】
instance method Object#freeze
漢字とフリガナを利用したユーザーのダミーデータを生成する、と生年月日のダミーデータ
これまでダミーデータを作成するにはFaker
しか利用したことが無かったので、「え、漢字の名前?しかもフリガナも?」と戸惑いましたが、検索すると見事にぴったりなライブラリを見つけました。
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作ってくれた方本当にありがとうございます・・・。
参考サイト
【Rails】gem ‘gimei’ で日本語のダミーデータを生成する
【日本語版Faker】「gimei」というGemについて
追加で修正したところ
一旦レビューをお願いして返ってきた修正点です。
factory :user do
# 中略
password = Faker::Internet.password(min_length: 6)
このままだと、'Faker'が数字だけのパスワードを生成する可能性があり、エラーに引っかかる可能性があるので
password = '1a' + Faker::Internet.password(min_length: 6)
に修正しました。
商品出品機能
大まかな流れ
- itemモデル・itemsコントローラーの生成
-
gem 'active_hash'
gem 'mini_magick'
gem 'image_processing'
をインストール - viewsディレクトリのitemsディレクトリに配布されたビューファイルを配置
- カラムを配置
- itemsコントローラーにストロングパラメーター・newアクション・createアクションを定義
- itemモデルにバリデーションを設定
つまずいた所
- 画面上で金額を計算させる
JavaScript
のコーディング -
:price
カラムのバリデーションに値の範囲を設ける
解決法
画面上で金額を計算させる
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%の手数料と利益額を出せます。
参考サイト
JavaScriptのMath.floorメソッドの使い方を現役エンジニアが解説【初心者向け】
バリデーションに値の範囲を設定する
ユーザーが設定できる金額は¥300~¥9,999,999なので、この範囲しか入力できないよということをバリデーションに設定しないといけません。
with_options presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 300, less_than_or_equal_to: 9999999 } do
validates :price
end
これでバリデーションに値の範囲を設けることができました。
参考サイト
numericalityを使って属性に数値のみが使われている事を検証する
商品一覧表示・編集・削除機能
ここはサクサク進めました。
DRYの法則が抜けがちなので気をつけます。
商品購入機能
ここでは、大きく2つの新しいことに取り組みました。
①オープンAPIの導入
②Formオブジェクトパターン
です。
商品購入機能を実装した
アプリケーションが購入機能を実装する際、ユーザーから直接カードの情報を受け取って決済を処理するのは、事務手続きが多い点、一定のセキュリティー基準を担保するのが難しい点から推奨されません。
そこで、クレジットカード決済代行サービスを介して実装します。今回はPAY.JP
を使用しました。
処理の流れとしては
①ユーザーがカード入力画面にカード情報を入力
②①をトークン化し、そのトークンをサーバーサイドに送信
します。
また、今回のアプリでは一つのフォームで①トークン②購入情報(購入したユーザーのid
と購入された商品のid
記録)③配送先情報の、テーブルが異なる(あるいはない)3つの情報を1度に保存する必要があります。これまでは1フォームに対し1テーブルしか対応してないアプリケーションしか作ったことがありませんでした。ここで使用するのがFormオブジェクトパターンです。
流れとしては
①モデルディレクトリ直下に新しいファイルを作成し、クラスを定義してActiveModel::Model
をinclede
②attr_accessor
で、今回のフォームで利用する全ての属性を使えるようにする
③バリデーションの処理を記述
④受け取ったデータの保存の処理について記述
てな感じです。
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
の値も扱えるようになります。これには値の取得を可能にするゲッター
と値の更新を可能にするセッター
を自動的に行ってくれるから、ということなのですが、今度もう少し深ぼってみようと思います。
あと途中つまずいたのが、購入した後にSold Out
の文字を入れる所です。
購入したかどうかで表示を変える方法
ここは、各商品のデータに「order
のデータが存在しているか?」という条件分岐をビューファイルに記述します。
<% if item.order.present? %>
<div class='sold-out'>
<span>Sold Out!!</span>
</div>
<% end %>
あとはBasic認証&デプロイして終了です。
正直初めましてのことが多くて戸惑いましたがなんとか基本実装は達成できました。
これからオリジナルを作成するのでさらにゴールが見えなくなりますが頑張ります。
これからは情報整理するためにこまめに書きたいと思います。