RailsでTimeクラスを使って有効期限付きクーポン機能を作ってみた

# 概要

Railsで時間の経過に伴って動的に処理が変化をするクーポン機能を作ってみました。
Storeがまずは存在をしていて、Cuponを画像で貼るのではなく、サービスから直接作成をできる機能になります。
今想定している個人開発中のアプリだと対象は中小の小売店等になるので、そこまで手間をかけなくても本格的なクーポンが簡単に作成できる機能を作成をしたいと考えておりました。

思ったよりかも簡単にできたので、流れを記載をしていきます。

<img width="886" alt="期間限定クーポン.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451174/fa6fdc06-ae7d-671f-eed6-72c1fc94d073.png">

クーポンに必要な要素としてはざっくり下記のようなものだと考えています。

・商品と対象の相手や具体的な割引が記載をされている
・但し書きがある
★有効期限があり、期限の設定ができる

特に一番下の有効期限に関してを中心に見ていきます。


# 実際の実装について

## 開発環境

・言語/フレームワーク:Ruby2.5.1,Ruby on Rails5.2.1
・CSSフレームワーク:bootstrap4
※画像アップロードにはgem：carriewaveを使っています。

方針としては下記のようにやっていきます。
・単純な投稿機能の作成
・有効期限機能の追加

## 単純な投稿機能の作成

まずはテーブルを作っていく必要があります。

```rb
class CreateCupons < ActiveRecord::Migration[5.2]
  def change
    create_table :cupons do |t|
      t.references :store
      t.text :reason　#セールの理由など
      t.string :product　#具体的な商品名
      t.string :discount　#割引やサービス内容
      t.integer :status, default: 0   #クーポンが有効なのか無効なのか
      t.string :image,　# クーポンのイメージ画像
      t.string :writing　# 但し書き
      t.integer :limit　# 有効期限
      t.timestamps
    end
  end
end
```

この中で特徴的なのはとlimitとstatusです。
今回はlimitに関しては確認をしやすくするため、制限時間（分）単位で指定をして、投稿時点から（limit）分後に表示が消える仕組みにしていきます。
そして、表示をするかどうかをstatusで判断をします。数字だと判断をしづらいのでenumを使ってopen,closedで判定をしていきます。
default:0にしているので、クーポンを作成をした直後はそのままopenになる仕様です。

```rb:cupon.rb
class Cupon < ApplicationRecord
  enum status: { open: 0, closed: 1 }
  mount_uploader :image, ImageUploader
  belongs_to :store
end
```
これで簡単に定義ができました。
また、今回storeモデルに従属をさせましたが、storeとcuponで1体多の関係になっているので、storeテーブルも変更をしてアソシエーションをさせます。routes.rbではcuponをネストをさせます。

そして、まずは時間経過を関係なく、投稿をできるようにしていきます。
cupon.new.rbをいじります。ここは特段の注意点はないです。

新規で投稿ができるようになりましたら、作成をしたクーポンのビューも作成をしていきます。

```rb:store/show.html.erb
    <h4 class="mt-2">期間限定クーポン一覧</h4>
    <div class="card-group mt-2">
      <% @cupons.each do |cupon| %>
        <%= render partial: 'cupons/cupon', locals: { cupon: cupon } %>
      <% end %>
    </div>
```
renderで切り出したcuponの部分が下記です。

```rb:_cupon.html.erb
<div class="card col-3">
  <% if cupon.image? %>
    <%= image_tag cupon.image.url,height:130,class:'card-img-top',alt:"Card image cap"%>        
  <% else %>
    <%= image_tag 'user_image.png',height:130,class:'card-img-top',alt:"Card image cap"%>        
  <% end %>
  <div class="card-body">
    <h5 class="card-title"><%= cupon.reason%></h5>
    <span class="card-text"><%= cupon.product %> </span>
    <span class="card-text" style="font-size :large"><%= cupon.discount %></span>
    <p class="card-text" style = "font-size : small"><%= cupon.writing %> </p>
    <p class="card-text" style = "font-size : small">有効期限<%= (cupon.created_at+cupon.limit.minutes).strftime("%Y-%m-%d %H:%M:%S") %> </p>
    <% if store_signed_in? && current_store.id == cupon.store.id %>
      <%=link_to "編集",edit_store_cupon_path(current_store,cupon.id),class: "btn btn-warning  text-white" %>
    <% end %>
  </div>
</div>
```
ここでポイントになってくるのは有効期限の部分です。
`.minutes`を使えば、数値を時間の（分）に変換をすることができます。そのため、そのままcreated_atにlimitを足せば有効期限が表示をされます。
※この段階だとまだあくまでビューの表示を変えているだけです。

## 有効期限機能の追加

長くなってしまっていますがここから有効期限機能を追加をしていきます。
時間を使って実装をするのには様々な方法があり、wheneverを使った方法やDelayedJobやSidekiqを使う方法等もあります。
今回のクーポンに関しては時間によって表示非表示を変えていきたいのですが、特段その後に処理がいらないシンプルな仕様です。
そのため、該当のページを表示をしたら判断をする方式をとります。
今回の方法であれば完全にWebサーバーだけで完結することもメリットのようです。


実際に記載をしていきます。

```rb:stores_contoroller.rb
class StoresController < ApplicationController
  before_action :timepass,only:[:show]

  def show
    @store = Store.find(params[:id])
    @cupons = Cupon.where(store_id: params[:id]).where(status:"open").limit(4).order("created_at DESC")
  end

  def timepass
    time = Time.now
    cupons = Cupon.all
    cupons.each do |c|
      if c.created_at + c.limit.minutes < time && c.status =="open"
        c.status = "closed"
        c.save
      end
    end 
  end

```
該当のページにアクセスをしたら処理をさせるので、`before_action :timepass,only:[:show]`を定義をします。
このメソッドの中では`time`に現在の時刻を記載をします。そして、cuponsで現在作成済みのCuponのインスタンスを取得をします。
そして、それぞれの募集中のインスタンスに対して、現在の時刻とlimitで設定をした制限時刻を足した時間＞現在時刻ならstatusをclosedに変更をするようにします。

最後に表示をするのは上記のshowメソッドでstatusがopenなものだけとしておけば、完成です。

もし設定した時間を経過をして、こちらのページにアクセスをした際はtimepassメソッドが働いて、有効期限が過ぎたクーポンは非表示になります。

これで完成です！

# まとめと今後の改善点

・時間を用いた処理の仕方としては様々なものがあるが、ユーザーのアクセスをもとに処理をするのが一番簡単。
・時間は比べたり、足したりすることが可能なので、組み合わせ次第で色々なロジックをつくることができる。
・さらにクーポンっぽくするには一回ユーザーが使ったら使えなくなるとかの機能もあると便利。
・今回は作成をした瞬間から使えてしまうが、いつから表示をされるのかも指定をできたほうがいい。
・有効期限を表す、limitはinteger形式にしたが、本来time形式のほうが直感的にできる。投稿画面ではカレンダー表示みたいにしていつからいつまでかを作成をできると直感的になる。
・若干ビューが複雑になってしまうので、ヘルパーメソッドを使ってすっきりとさせたほうがよさそう。

#　参考にした情報
https://qiita.com/chroju/items/f17ae01630f7e4ac03c1
https://teratail.com/questions/66535
https://teratail.com/questions/13922