1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

概要

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

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

フリマアプリ

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

Ruby on Rails 6.0.0を使用

データベース設計

気になったところ

  • 必要なテーブルはユーザー、商品、購入記録、配送先
  • 日付を管理するカラムはdate型にする
  • 基本的に変更されない静的データはActiveHashを用いる→送る値は整数
完成したDB
## 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

ED図
スクリーンショット 2020-12-16 15.05.20.png

ユーザー管理機能

大まかな流れ

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

つまずいた所

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

解決法

`password`のバリデーション設定

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

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

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

参考サイト
【Rails】deviseのバリデーションをカスタマイズ
Active Record バリデーション

`email`のバリデーション設定

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

参考サイト
【Rails】メールアドレスのバリデーション
【Rails】メールアドレスの一意性の検証【Rails Tutorial 6章まとめ】
instance method Object#freeze

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

これまでダミーデータを作成するには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作ってくれた方本当にありがとうございます・・・。
参考サイト
【Rails】gem ‘gimei’ で日本語のダミーデータを生成する
【日本語版Faker】「gimei」というGemについて

追加で修正したところ

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

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)

に修正しました。

商品出品機能

大まかな流れ

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

つまずいた所

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

解決法

画面上で金額を計算させる

作りたいのがこれ
1e92f5550e5e4dd2f60f79b3a8b681a0.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%の手数料と利益額を出せます。
参考サイト
JavaScriptのMath.floorメソッドの使い方を現役エンジニアが解説【初心者向け】

バリデーションに値の範囲を設定する

ユーザーが設定できる金額は¥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

これでバリデーションに値の範囲を設けることができました。
参考サイト
numericalityを使って属性に数値のみが使われている事を検証する

商品一覧表示・編集・削除機能

ここはサクサク進めました。
DRYの法則が抜けがちなので気をつけます。

商品購入機能

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

商品購入機能を実装した

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

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

また、今回のアプリでは一つのフォームで①トークン②購入情報(購入したユーザーのidと購入された商品のid記録)③配送先情報の、テーブルが異なる(あるいはない)3つの情報を1度に保存する必要があります。これまでは1フォームに対し1テーブルしか対応してないアプリケーションしか作ったことがありませんでした。ここで使用するのがFormオブジェクトパターンです。
流れとしては
①モデルディレクトリ直下に新しいファイルを作成し、クラスを定義してActiveModel::Modelinclede
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の値も扱えるようになります。これには値の取得を可能にするゲッターと値の更新を可能にするセッターを自動的に行ってくれるから、ということなのですが、今度もう少し深ぼってみようと思います。

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

購入したかどうかで表示を変える方法

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

views/order/index.html
<% if item.order.present? %>
  <div class='sold-out'>
    <span>Sold Out!!</span>
  </div>
<% end %>

参考サイト
Railsでnil? blank? empty? present?を使いこなそう

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

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?