#はじめに
PFでジャニーズの私物を特定して、すぐに購入できるサービスを作成したのでまとめます。
あまりにも長くなりそうだったので、4回に分けてまとめています。
今回が最後です使用したgem
についてまとめます。
#サービスの概要
ジャニーズファンは推しと同じものを日常生活やライブで身につけたいという心理があります。
twitte上にファンの人が私物を特定してツイートをするので、そのツイートをキャッチして、LINEで通知・購入できるサービスになっています。
#なぜこのサービスを作成したか
私物の特定のツイートがされるとすぐファンが購入し、売り切れてしまうことがよくあります。
私自身、私物特定のツイートを発見し、サイトを検索してももう売り切れてしまっていたということが何回もあります。
他のファンよりも早く購入できるサービスがあればいいなと思い、このサービスを作成しました。
##完成したサービス
Webサイトは以下になります↓
LINEのQRコードになります。
ぜひ動かしてみてください!!
サービスのイメージがしづらいと思いますがこんな流れになります
1.Twitterで私服特定ツイートがされます。
LINE友達登録後できること
1.最新の情報を確認できます。
リッチメニュー右上の最新の情報ボタン
を押す→情報を確認したいメンバーを選択
する→最新の情報
が表示される
2.情報を受け取るメンバーを選択できます。
リッチメニュー左上のメンバー設定を変更する
を選択する→設定したいメンバーを選択
する→通知を受け取る
か、受け取らない
かを選択する
もちろん、現在の通知設定についてもリッチメニュー左下のメンバー設定を確認する
から確認可能です。
##使用した機能
Twitter API
LINE Messaging API
dotenv-rails'
config gem
ransack gem
sorcery gem
kaminari gem
enum_help gem
rails-i18n gem
AWS
heroku
##画面遷移図
##他に使用しているgemについて
他にも今回導入しているgemがあるのでまとめていきます。
####whenever gem
gem 'whenever', require: false
cron
の設定を簡単な文法で書けます。
定時実行の処理をwhenever
を使うと設定できます。
今回は、Twitterの検索をrakeタスクに作成して、定期的に私物に関するツイートがないか検索をかけました。
デプロイにはheroku
を使用しました。heroku
はwhenever gem
に対応していないため、scheduler
を使用して定期実行を行っています。
❶lib/tasks以下にcronを使用して、定期的に実行する処理のかたまり(rakeタスク)を作成します。
herokuでは10分に1回しか定期実行できない為、3.times
やsleep(150)
を使用して、時間の帳尻を合わせています。
namespace :make_goods_find_article do
desc '「GoodsFind」のツイートが私物に関するものかを判別し私物に関するものであれば記事を作成する'
task account_goods_find: :environment do
3.times{
#3回行っています。
article = Article.new
article.make_GoodsFind_article
#article.rbで作成したmake_GoodsFind_articleメソッドを実行
sleep(150)
#150秒経ったら次の処理へ
}
end
end
❷config/suchedule.rb
を作成して、定期実行したいタスク設定(実行の間隔、時間)を書き込んで反映させます。
以下のコマンドをアプリディレクトリ内で実行します。
実行すると、config/suchedule.rb
が作成されます。
$ wheneverize .
ここに定期実行したいタスク設定(実行の間隔、時間)を書き込んで反映させます。
# Rails.rootを使用するために必要
require File.expand_path(File.dirname(__FILE__) + '/environment')
# cronを実行する環境変数
rails_env = ENV['RAILS_ENV'] || :development
# cronを実行する環境変数をセット
set :environment, rails_env
# cronのログの吐き出し場所
set :output, "#{Rails.root}/log/cron.log"
# 10分ごとに実行
every 10.minute do
rake "make_goods_find_article:account_goods_find"
end
❸設定を反映させます。
設定内容にエラーがないか確認
$ bundle exec whenever
cronにデータを反映させるコマンド
$ bundle exec whenever --update-crontab
参照(開発中はこの記事を参考にして、定期実行を行いました。)
heroku
のscheduler
を使用し定期実行した方法はこちらの記事にまとめてあります。
####ransack gem
# 検索
gem 'ransack'
検索部分をこのgemで作成しました。
このような感じで、ブランド名
とメンバー名
から検索できるように作成しました。
今回は、article一覧ページのヘッダーに検索フォームを作成したいです。
❶まずメソッドを確認します。
params[:q]
→ この後に作成するビューファイルから送られてくるパラメーターです。
ransack
メソッド → 送られてきたパラメーターを元にテーブルからデータを検索するメソッドです。
(whereメソッドのransack版というイメージです。)
result
メソッド → ransackメソッドで取得したデータをActiveRecord_Relationのオブジェクトに変換するメソッドです。
❸controllerを確認します。今回はヘッダー部分に検索フォームを作成したいので、application_controller.rb
とcontrollers/articles_controller.rb
とcontrollers/admins/articles_controller.rb
に作成します。
共通で使用するメソッドを作成します。
class ApplicationController < ActionController::Base
def set_item_search
@q = Article.ransack(params[:q])
#ransackメソッドを使用しparams[:q]でviewから送られてくるパラメーターを元にテーブルからデータを検索する。
@set_items = @q.result
#ransackメソッドで取得したデータをActiveRecord_Relationのオブジェクトに変換する
end
end
def index
@articles = @set_items.includes(:members).published.order(created_at: :desc).page(params[:page]).per(6)
end
def index
@articles = @set_items.includes(:members).order(created_at: :desc).page(params[:page])
end
❸検索フォームのviewを確認します。
共通で使用する検索フォームはパーシャルにしました。
<div class = "search mx-auto py-4">
<%= search_form_for @q do |f| %>
<%= f.search_field :brand_cont, class: " search_btn btn-outline-dark", placeholder: t("activerecord.attributes.article.brand") %>
<%= f.search_field :members_name_cont, class: " search_btn btn-outline-dark", placeholder: Member.model_name.human %>
<%= f.submit 'search', class: "search_btn btn-outline-dark"%>
<% end %>
</div>
ヘッダーでそのときのpathに合わせて表示する形にしました。
<% if current_page?(articles_path) %>
<%= render partial: 'layouts/search', url: articles_path %>
<% elsif current_page?(admins_articles_path) %>
<%= render partial: 'layouts/search', url: admins_articles_path %>
<% end %>
####sorcery gem
# ログイン
gem 'sorcery'
認証のための最小限のロジックを積んだgemで、ユーザーの必要に応じてメソッドを増やしたり拡張をしていくことを目的としています。
以下の点を考えて今回はsorcery
を使用しました。
使いたいのはログイン機能のみです。
deviseに比べて重くない
カスタマイズしやすい。
❶コマンドを実行し必要なファイルを生成します。
$ rails generate sorcery:install
↓のファイルが生成されます。
create app/models/user.rb
create db/migrate/XXXXXXXXX_sorcery_core.rb
(1)db/migrate/XXXXXXXXX_sorcery_core.rbについて
class SorceryCore < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :email, null: false
t.string :crypted_password
t.string :salt
t.integer :role, default: 0
t.timestamps
end
end
end
salt
について
標準の暗号化では、「ソルト」を使用して、パスワードハッシュの安全性を高めます。Sorceryでは、パスワードの末尾にランダムな文字列を結合し、その文字列をソルトフィールドに記憶することでこれを行います。
$ rails db:migrate
で反映させます。
今回は、ユーザー作成ページは作成しませんでした。
管理者のみ必要なので、一般ユーザーはログインせずに使用できる流れになります。
なので、ログイン用のviewやcontrollerのみ作成しています。
####ログイン用のviewやcontroller
ログイン・ログアウト機能の実装についてです。sorceryで提供しているメソッドについては、公式のこちらのに記述があります。
loginメソッドは、email、password、remenber_me(デフォルト値はfalse)の3つの引数を取ることができると分かります。
今回はemail、passwordを使用してログインさせます。
class Admins::SessionsController < ApplicationController
skip_before_action :require_login, only: %i[create new]
def new;end
def create
@user = login(params[:email], params[:password])
if @user
redirect_back_or_to admins_articles_path, success: t('flash.login')
else
flash.now[:danger] = t('flash.not_login')
render :new
end
end
def destroy
logout
redirect_back_or_to admins_login_path, success: t('flash.logout')
end
end
ここのrequire_login
について
skip_before_action :require_login, only: %i[create new]
require_login
メソッドはSorceryで提供されているメソッドです。
ログインしていないユーザーをアクション単位で弾き、not_authenticated
メソッドを発火します。
not_authenticated
はSorceryで提供されているメソッドです。
デフォルトではredirect_to root_pathが定義されています。
自分で変更したい際には、application_controller.rb
で記述します。
class ApplicationController < ActionController::Base
add_flash_types :success, :info, :warning, :danger
#add_flash_typesメソッドで指定したキーがredirect時のflashで使えるようになる。
#通常はnoticeとalertだけだが、任意のキーを指定できるようになる。
before_action :require_login
private
def not_authenticated
redirect_to admins_login_path, danger: t('flash.before_login')
end
end
それぞれまとめているページはこちらになります。
####rails-i18n gem
# ja.yml
gem 'rails-i18n'
デフォルトの言語を日本語に設定します。
config/locales/ja.yml
を作成し、日本語に対応させます。
❶config/application.rb
の設定を変更します。
config.i18n.default_locale = :ja
#デフォルトの言語を日本語(ja)にします。
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
#i18nの複数ロケールファイルが読み込まれるようpathを通します。
❷config/locales
以下にロケールファイルを配置し、日本語を設定します。
ja:
activerecord:
models:
member: 'Member'
report: 'REPORT'
attributes:
article:
brand: 'Brand'
member: 'メンバー'
twitter_url: 'TwitterへのURL'
member:
email: 'メールアドレス'
password: 'パスワード'
####enum_help gem
# enum
gem 'enum_help'
enumで定義した値をi18n化させることができます。
i18n対応セレクトボックスを作成することも可能です。
#####❶enumで定義した値をi18n化はこのように設定し、使用します。
ja:
enums:
user:
role:
other: '一般'
admin: '管理者'
#####❷今回はi18n対応セレクトボックスも作成、使用しています。
以下の形で、編集ページで公開
か非公開
かを選択できるようにしています。
<%= form_with model: @article, url: admins_article_path(@article.id), local: true do |f| %>
#省略
<%= f.select :status, Article.statuses_i18n.invert %>
#省略
<% end %>
ポイントはセレクトボックスの値です。
select(オブジェクト名, メソッド名, 要素(配列 or ハッシュ) [, オプション or HTML属性 or イベント属性])
とすればいいので、以下のようにすれば動きます。
<%= f.select :status,[["公開中", "published"], ["非公開", "draft"]] %>
# f.select :プロパティ名,[["表示される文字", "渡される値"], ["表示される文字", "渡される値"]]
ですが、enum_help
を使用するともっと簡単に書けますし、enum
に変更があったとしてもコードを変更する必要はありません。
↓このように書くことができます。
<%= f.select :status, Article.statuses_i18n.invert %>
上で見た以下のコードは以下のような構成になっていたので、
f.select :プロパティ名,[["表示される文字", "渡される値"], ["表示される文字", "渡される値"]]
enum_help
を使用して[[表示される選択肢, 渡される値], [表示される選択肢, 渡される値]]
という形にして渡すには...
irb(main):012:0> Article.statuses
=> {"published"=>0, "draft"=>1}
irb(main):013:0> Article.statuses_i18n
=> {"published"=>"公開中", "draft"=>"非公開"}
irb(main):014:0> Article.statuses_i18n.map { |k, v| [v, k] }
=> [["公開中", "published"], ["非公開", "draft"]]
irb(main):015:0>Article.statuses_i18n.invert
#値からキーへのハッシュを作成
=> {"公開中"=>"published", "非公開"=>"draft"}
invert
メソッド
値からキーへのハッシュを作成して返します。
select(オブジェクト名, メソッド名, 要素(配列 or ハッシュ) [, オプション or HTML属性 or イベント属性])
の通り、配列 or ハッシュを渡せばいいので、以下の形で同じように動きます!もし並列にしたければto_a
メソッドを最後に使用すれば配列になりますが、必要ないので使用しません。
Article.statuses_i18n.invert
#値からキーへのハッシュを作成
=> {"公開中"=>"published", "非公開"=>"draft"}
####kaminari gem
# ページネーション
gem 'kaminari'
1つのページに表示する記事の数を指定し、複数ページに分けて表示させるようにするgemです。
今回は1ページに8つの記事を表示させています。
8個以上の記事がある場合は以下を表示し、別ページに表示されています。
❶設定ファイルを作成し、デフォルトの表示数を設定します。
$ rails g kaminari:config
config/initializers/kaminari_config.rb
が作成されます。
ここで、表示数を指定します。デフォルトは25が設定されています。
今回は8で設定します。
Kaminari.configure do |config|
config.default_per_page = 8
#省略
end
❷controllerで表示数などを指定します。
デフォルトで8を指定したので、8こ表示したい場合は記載は必要ないですが、この一覧部分は6個を指定したかったので、.per(6)
を使用して数を指定しています。
def index
@articles = @set_items.includes(:members).published.order(created_at: :desc).page(params[:page]).per(6)
end
❸view部分を指定します。
❷のコントローラー、アクションでレンダリングされるページを例にすると...
以下を記載するだけで表示されます。
<%= paginate @articles %>
❹デザインを変更します。bootstrap4を導入し、自分で後から少し変更しています。
$ rails g kaminari:views bootstrap4
#####aws-sdk-s3 gem
gem "aws-sdk-s3", require: false
Heroku には “一時的” ハードドライブ
があります。これは、ファイルをディスクに書き込むことはできますが、アプリケーションを再起動するとそれらのファイルは失われてしまいます。
activestorageを使用すると、しばらくは問題ありませんが、添付ファイルは表示動作に支障をきたすようになり、最終的には消えてしまいます。
それが起きないように、アップロードしたファイルをディスクに保存する代わりに、クラウドファイルストレージサービスを使用します。
それに使用するのがこのaws-sdk-s3 gem
です
#####config gem
定数管理を行います。
今回は、Twitterのアカウントidなどをここで管理しました。