【Rails】クーポン機能の実装(バッチ処理を用いた自動削除機能付き)

# 目標
![ezgif.com-video-to-gif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/bfdf64fb-3ea0-732c-9bd1-2a4de401f84d.gif)

# 開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

# 前提

下記実装済み。

・[Slim導入](https://qiita.com/matsubishi5/items/b639ffddecc626856039)
・[Bootstrap3導入](https://qiita.com/matsubishi5/items/b117c95f4dd71340cc80)
・[ログイン機能実装](https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d)
・[投稿機能実装](https://qiita.com/matsubishi5/items/1a5aeab651b5822d1f2c)

# 実装

## 1.カラムを追加

```terminal:ターミナル
$ rails g model Coupon user_id:integer is_valid:boolean limit:integer
```

```ruby:~__create_coupons.rb
class CreateCoupons < ActiveRecord::Migration[5.2]
  def change
    create_table :coupons do |t|
      t.integer :user_id
      t.boolean :is_valid, default: true # 「default: true」を追記
      t.integer :limit

      t.timestamps
    end
  end
end
```

```terminal:ターミナル
$ rails db:migrate
```

## 2.モデルを編集

```ruby:user.rb
# 追記
has_many :coupons, dependent: :destroy
```

```coupon.rb
class Coupon < ApplicationRecord
  belongs_to :user

  enum is_valid: { '有効': true, '無効': false }

  def self.coupon_create(user)
    coupon = Coupon.new(user_id: user.id, limit: 1)
    coupon.save
  end

  def self.coupon_destroy
    time = Time.now
    coupons = Coupon.all
    coupons.each do |coupon|
      if coupon.created_at + coupon.limit.days < time && coupon.is_valid == '有効'
        coupon.is_valid = '無効'
        coupon.save
      end
    end
  end
end
```

### 【解説】

#### ① クーポンの状態をenumで管理する。

```ruby
enum is_valid: { '有効': true, '無効': false }
```

#### ② クーポンを作成するメソッドを定義する。

```ruby
def self.coupon_create(user)
  coupon = Coupon.new(user_id: user.id, limit: 1)
  coupon.save
end
```

#### ③ クーポンを削除するメソッドを定義する。

```ruby
def self.coupon_destroy
  time = Time.now
  coupons = Coupon.all
  coupons.each do |coupon|
    if coupon.created_at + coupon.limit.days < time && coupon.is_valid == '有効'
      coupon.is_valid = '無効'
      coupon.save
    end
  end
end
```

**◎ クーポンを作成してから24時間経過かつ、クーポンの状態が有効の場合は、無効に変更して保存する。**

```ruby
if coupon.created_at + coupon.limit.minutes < time && coupon.is_valid == '有効'
  coupon.is_valid = '無効'
  coupon.save
end
```

## 3.`coupons_controller.rb`を作成・編集

```terminal:ターミナル
$ rails g controller coupons index
```

```ruby:coupons_controller.rb
class CouponsController < ApplicationController
  def index
    @coupons = Coupon.where(user_id: current_user.id, is_valid: '有効') 
  end
end
```

## 4.`books_controller.rb`を編集

今回は本を投稿成功した場合に、クーポンを発行するように実装します。

```ruby:books_controller.rb
def create
  @book = Book.new(book_params)
  @book.user_id = current_user.id
  if @book.save
    Coupon.coupon_create(current_user) # 追記
    redirect_to books_path
  else
    @books = Book.all
    render 'index'
  end
end
```

## 5.日時設定を変更

### ①`application.rb`を編集する。

```ruby:application.rb
module Bookers2Debug
  class Application < Rails::Application
    config.load_defaults 5.2
    config.time_zone = 'Tokyo' # 追記
  end
end
```

### ②日時のフォーマットを設定するファイルを作成・編集

```terminal:ターミナル
$ touch config/initializers/time_formats.rb
```

```ruby:time_formats.rb
Time::DATE_FORMATS[:datetime_jp] = '%Y/%m/%d/%H:%M'
```

## 6.ビューを編集

```slim:coupons/index.html.slim
.row
  .col-xs-3

  .col-xs-6
    table.table
      thead
        tr
          th
            | クーポン番号
          th
            | タイトル

      tbody
        - @coupons.each.with_index(1) do |coupon, index|
          tr
            td
              = index
            td
              - limit = coupon.created_at + coupon.limit.minutes
              = limit.to_s(:datetime_jp)

  .col-xs-3
```



### 【解説】

#### ① クーポン作成日時の1日後を、`5`で設定したフォーマットで表示する。

```slim
- limit = coupon.created_at + coupon.limit.minutes
= limit.to_s(:datetime_jp)
```

## 7.自動削除機能の実装

### ① Gemを導入

```ruby:Gemfile
# 追記
gem 'whenever', require: false
```

```terminal:ターミナル
$ bundle
```

### ② `「schedule.rb」`を作成・編集

```terminal:ターミナル
$ bundle exec wheneverize .
```

```ruby:config/schedule.rb
env :PATH, ENV['PATH'] # 絶対パスから相対パス指定
set :output, 'log/cron.log' # ログの出力先ファイルを設定
set :environment, :development # 環境を設定

every 1.minute do
  runner 'Coupon.coupon_destroy'
end
```

###4.`cron`を反映

```terminal:ターミナル
$ bundle exec whenever --update-crontab
```

### バッチ処理でよく使うコマンド

`crontab -e` ➡︎ cronをターミナル上で編集

`$ bundle exec whenever` ➡︎ cronの設定を確認

`$ bundle exec whenever --update-crontab` ➡︎ cronを反映

`$ bundle exec whenever --clear-crontab` ➡︎ cronを削除