21日目：H30秋基本情報技術者試験の問3データベース

前回のアウトプットから、だいぶ間が空いたが、今回から暫く、基本情報技術者試験問題に向き合う。

コンサートチケットの予約販売サイト、特にチケット販売サブシステムを用いる問題だった。
特に、今回のスクールの大成すべく、問3は実際にDBのみならず、Appも作成したい。
出典： [平成30年度 秋期 基本情報技術者試験（FE）試験区分 午後 問1 問3](https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2018h30_2/2018h30a_fe_pm_qs.pdf)

#DB仕様、ER図
Cacooを用いてみた。カラム名が冗長な気がする。
![Entity Relationship Diagram.png](https://qiita-image-store.s3.amazonaws.com/0/294402/aad97425-2d2d-4c00-379e-6dab63b10d4b.png)


#販売サブシステムの(引用、一部編集）
1. コンサートの席種はS,A,Bがあり、コンサート毎にその価格と発売席数は異なる。
2. 取り扱うコンサートの席種（S,A,B)毎の販売可能な席数を管理する。
3. 会員が購入申込すると、一意な販売IDを生成して販売表にレコード追加
4. 会員が支払いをすると、決済処理として決済表に追加。
5. 決済期限の翌日に、決済期限を過ぎた販売表中のレコードと販売IDが同じレコードが決済表にない場合、購入申込は取り消され、バッチ処理として、決済表に当該販売IDを主キーとして決済表に決済日をnull、決済額を-1としてレコード追加
6. バッチ処理は毎夜0~4時の販売サイトのシステムメンテナンス時に行う
7. 購入申込席数が販売可能な席数を上回る場合は、販売終了と表示し、購入申込を受け付けない
等々。。。

※注意事項
コンサート席種のカラムが、商品詳細表と販売表に1つずつあり、重複していて気持ち悪いが、
試験問題がそうなっており、敢えてそうしてる模様なので、そのままにする。
その意図を反映するため、席種の商品詳細表側はinteger、販売表側はstringにしておく。

#使用環境
- ホストOS: Windows10 Home
- 仮想環境OS: Ubuntu Bento/Bionic
- Ruby：2.51
- Rails: 5.2.2 
    - 暫定追加gem : [devise](https://github.com/plataformatec/devise)
- DB: PostgreSQL

##rails gコマ
今回もdeviseを利用することを、頭に入れておきたい。

```:terminal
rails new concert_ticket -d postgesql
```
```:Gemfile
# miniracerをアンコメント
gem 'miniracer'
# authentication
gem 'devise'
```
```:terminal
bundle
```
```:database.yml
username: admin
password: 〇×△
host: localhost
```
```:terminal
rails db:create
```
```:terminal
# deviseのインストール
rails g devise:install
# Userモデルにdeviseを追加。モデルなので、単数形
rails g devise User name:string email:string point:integer

# 商品表と商品詳細表テーブルはscaffoldを用いる
rails g scaffold Concert concert_info:text concert_date:datetime
rails g scaffold ConcertDetail concert:references seat_grade:integer seat_price:integer tickets_total:integer

# Saleモデルの追加
# カラムが多い。販売表モデル 商品表外部キー　会員表外部キー　席種　　　　　　　　席数　　　　　　　　　販売日　　　　　　　　販売額　　　　　　　　決済期限日　　　　　　　　　使用ポイント　　　　　　　
rails g model Sale concert:references user:references seat_grade:string seats_total:integer sale_date:datetime sales_total:integer payment_deadline:datetime used_point:integer

# 決済表モデルを追加
rails g model Payment sale:references payment_date:datetime payment_total:integer added_point:integer
```
##関連付け
```app/models/concert.rb
has_many :concert_detail
has_many :sale
```
```app/models/concert_detail.rb
belongs_to :concert
```
```app/models/user.rb
has_many :sale
```
```app/models/sale.rb
has_one :payment
belongs_to :concert
belongs_to :user
```
```app/models/payment.rb
belongs_to :sale
```
##userテーブルにadminカラム追加
単に入れ忘れた。

```:terminal
rails g migration AddAdminToUser admin:boolean
```
```/db/migrate/(timestanp)_add_admin_to_student.rb
class AddAdminToUser < ActiveRecord::Migration[5.2]
  def change
    add_column :students, :admin, :boolean,default: false
  end
end
```

##validation追加
後からさらに追加していくと思うが、次のデータ入力の為に。
なお、ファイル中のvalidation前後のコードは省略。

```app/models/user.rb
# name の空欄不可
validates :name, presence: true
#　emailの空欄不可、一意指定
validates :email, presence: true, uniqueness: true
#　emailの構成文字の大文字小文字の区別はしない
#　あとで、正規表現込みのvalidation追加等するが、あとまわし。
validates :email, presence: { case_sensitive: false }
```
```app/models/sale.rb
# 購入席数の空欄不可
validates :seats_total, presence: true
# 購入席数は4席まで。(試験問題には無いけど）
validates :seats_total, numericality: {less_than_or_equal_to: 4}
```

##初期データ作成 (seeds.rb使用)
普段はコンソールからが多いが、rails tutorialをやって、seeds.rbの方が勝手が良かったので。
あと、gem Fakerは、[SAO](https://github.com/stympy/faker/blob/master/doc/japanese_media/sword_art_online.md)など『[遊び](https://github.com/stympy/faker)』があって、好きだが、rubyメソッドの勉強にならないので、今回はパス
###ユーザ作成
カラムは氏名とemailアドレス

```db/seeds.rb
# 管理者
User.create!(name: 'おりばー', email: 'ないしょ@gmail.com', password: 'ないしょ', admin: true)

# 一般ユーザ。とりあえず、20人作る。
20.times do |i|
  name = (1..5).map { ('あ'..'ん').to_a[rand(20)] }.join
  email = ('a'..'z').to_a.shuffle[1..5].join

  User.create!(name: name, email: "#{email}-#{i}@gmail.com", password: SecureRandom.base64)
end
```
###商品表と商品詳細表の作成
(まだ足りないと思うが）リファクタリングの結果、コードが合わさったので、纏めて表示。
なお、qiita用に、一部コードの順序を変えてある、が、見づらい。

```db/seeds.rb
# concert
# 20回分のコンサート、20組分のアーティストを作成
require 'active_support/all'

concert_name = %w[雅楽 令和 ジャズ ラスト ロック アニソン]
local = %w[東京 札幌 名古屋 大阪 広島 宮崎 台南 Cebu Perth]
artists = []

# 20回分のコンサート、20組分のアーティストを作成
(1..20).each do |n|
  # コンサート開催日として、期間s1 ~ s2までのランダムな年月日を作成
  s1 = Date.new(2019,5,31)
  s2 = Date.new(2020,3,31)
  s = Random.rand(s1..s2)

  # 出演アーティストをランダムで作成して、配列に入れる。
  # 作成した配列の並びをシャッフルし、先頭から１～５アーティストを取得し、カンマで繋げた文字列に。
  artists << (1..3).map { ('A'..'Z').to_a[rand(20)] }.join.to_s
  who_join = artists.shuffle.take(rand(5)+1).join('、')

  # 試験問題の"クリスマスコンサート2018 in　東京　出演：Xバンド・・・"の表記に沿ってあげる。
  infomation = "#{concert_name.sample}コンサート #{s.strftime('%Y')} in #{local.sample} 出演：#{who_join}"
  # コンサート情報の開催年と、開催日時が合致するようにした。
  Concert.create!(concert_info: infomation, concert_date: s.strftime('%Y-%-m-%-d'))
  
  concert = Concert.find(n)
  (0..2).each do |i|
    # seat_price S > A > B
    # prices = [price_s , price_a , price_b]
    # capacity= [sの席数、aの席数、bの席数]
    prices = [[40000, 35000, 30000], [28000, 25000, 23000, 19000], [18000, 15000, 13000, 11000, 9000]]
    capacity = [rand(10..50), rand(100..500), rand(500..1000)]
    ConcertDetail.create!(concert:concert,seat_grade:i, seat_price:prices[i].sample, tickets_total: capacity[i-1])
  end      
end
```
###actrive_recordを使おう
[参照](http://railsdoc.com/active_support#%E6%97%A5%E4%BB%98%E9%96%A2%E9%80%A3)
##初期データ投入
```:terminal
ralis db:seed
```
##カラム名を変更しよう
思った。非常に分かりづらい。controller作成時に疲れそう。
会員表(User)テーブルはパス。あと、コードは一部省略

商品表（Concert)テーブル
現状カラム：コンサート情報：concert_info , 開催日時：concert_date
=>infomation , date

```db/migrate/(timestanp)_create_concerts.rb
    create_table :concerts do |t|
      t.text :infomation
      t.datetime :date
      t.timestamps
    end
```

商品詳細表(ConcertDetail)テーブル
現状：席種：seat_grade , 販売価格：seat_price , 販売席数：ticket_total
=> grade , price , capacity

```db/migrate/(timestanp)_create_concert_details.rb
create_table :concert_details do |t|
      t.references :concert, foreign_key: true
      t.integer :grade
      t.integer :price
      t.integer :capacity
      t.timestamps
    end
```

販売表(Sale)テーブル
席種：seat_grade、席数：seats_total、販売日：sale_date、販売額：sales_total、決済期限日：payment_deadline、使用ポイント：used_point
=> grade , number_of_seats , date , amount , payment_deadline , used_point
このテーブルは安易にリネームすると尚更分かりづらくなるので、注意した。
販売額amountとごっちゃにならず、席数に最適な短い訳が思いつかないので、一応英語に従った。、 


```db/migrate/(timestanp)_create_sales.rb
create_table :sales do |t|
      t.references :concert, foreign_key: true
      t.references :user, foreign_key: true
      t.string :grade
      t.integer :number_of_seats
      t.datetime :date
      t.integer :amount
      t.datetime :payment_deadline
      t.integer :used_point
      t.timestamps
    end
```
決済表(Payment)テーブル
決済日：payment_date、決済額：payment_total、付与ポイント:added_point
=> date , amount , added_point

```db/migrate/(timestanp)_create_payments.rb
create_table :payments do |t|
      t.references :sale, foreign_key: true
      t.datetime :date
      t.integer :amount
      t.integer :added_point
      t.timestamps
    end
```
seeds.rbファイルもカラム名を使用しているので、一部変更

```:terminal
rails db:migrate:reset
rails db:migrate
rails db:seed
```

##ER図を修正
![modified.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294402/e20b70c7-7718-02cd-12e9-7ae7e7aae101.png)

あとは、controllerとviewを弄っていくか。