セッションとクッキー

 1度ログインしたサービスにもう1度ログインしようとすると、すでにログイン状態になっていることがあると思います。これはセッションとクッキーという仕組みによってブラウザに情報が一時記憶されているからです。

 

セッションとは

 セッションは、初回のログインの際に取得したようなデータを、サーバーサイドで一時的に記憶する仕組みのことです。

 Ruby on Railsでも、ショッピングサイトでユーザー情報を保持したいといった場合にsessionが使用できます。sessionというハッシュに値を追加するような記述になります。

class TweetsController < ApplicationController
def index
session[:sample] = 1
end
end

 

クッキーとは

 このセッションの情報を保存する場所のことをクッキーと言います。この保存場所はデータベースではなくブラウザであり、クライアントサイドになります。このクッキーにセッションのIDを格納することで、サーバーにセッションのIDを送信できます。

 特にこのクッキーに、データを改竄される可能性があります。

 

 

 

 プログラミング初心者なりに頭を整理してみました。より正確な解釈ができるように日々勉強していきます。

 

 

 

 

 

 

 

 

ActionCableの実装

 コメントを即時更新するActionCableを用いた実装を紹介したいと思います。以下の流れで説明していきます。ブログの仕様上インデントが整っていないので、ご容赦ください。

 

1. チャンネルの作成

2. stream_formメソッドで関連づけ

3. broadcastを介する

4. comment_channel.jsの編集

5. Herokuでデプロイする場合

6. AWSでデプロイする場合

 

 

1. チャンネルの作成

 まず即時更新機能を実現するサーバー側の仕組みであるChannelファイルを作成します。データの経路を設定したり、送られてきたデータを画面上に表示させたりします。

ターミナル

% rails g channel comment

 

 

2. stream_formメソッドで関連づけ

 次にcomment_channel.rbを編集します。これはルーティングの機能を果たすファイルです。stream_formメソッドを用いることでサーバーとクライアントを関連づけます。

app/channels/comment_channel.rb

class CommentChannel < ApplicationCable::Channel
def subscribed
stream_from "comment_channel"
end

def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end

 

3. broadcastを介する

 コメントの保存が成功した時に、broadcastを介してコメントが送信されるように記述します。僕が行ったこの実装では、ユーザーや商品を紐付けてユーザー名も送信されるようにしています。

app/controllers/comments_controller.rb

class CommentsController < ApplicationController
def new
@comments = Comment.all
@comment = Comment.new
end

def create
@comment = Comment.new(comment_params)
@user = @comment.user
if @comment.save
ActionCable.server.broadcast 'comment_channel', { content: @comment,
comment_user: @user }
end
end

private

def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id,
item_id: params[:item_id])
end
end

 

 

4. comment_channel.jsの編集

 受け取った情報はreceivedの引数のdataに入ります。このデータをhtmlに挿入できるように記述します。

app/javascript/channels/comment_channel.js

import consumer from "./consumer"

consumer.subscriptions.create("CommentChannel", {
connected() {
// Called when the subscription is ready for use on the server
},

disconnected() {
// Called when the subscription has been terminated by the server
},

received(data) {
const html = `
<p>
<strong> ${data.comment_user.nickname}:</strong>
${data.content.text}
</p>`;
const comments = document.getElementById('comments');
const newComment = document.getElementById('comment-text');
comments.insertAdjacentHTML('afterbegin', html);
newComment.value='';
}
});

 

 これで即時コメントが表示される実装が完了です。

 

5. Herokuでデプロイする場合

  adapterの指定をasyncに変更します。これは無料で導入が可能なアダプターです。

config/cable.yml

development:
adapter: async

test:
adapter: test

production:
adapter: async
#adapter: redis

 

 ActionCableは指定されていないオリジンからのリクエストを受け付けない設定がされているため、設定を編集します。ブログ内に収めるためにやや記述が変更されています。

config/environments/production.rb

Rails.application.configure do
#省略
 
ActionCable.server.config.disable_request_forgery_protection = true
config.action_cable.url = "wss://[アプリ名].herokuapp.com/cable"
config.action_cable.allowed_request_origins =
['https://[アプリ名].herokuapp.com', 'http://[アプリ名].herokuapp.com']

#省略
end

 

 これで後はgit push heroku masterをすれば実装完了です。

 

6. AWSでデプロイする場合

 AWSでデプロイする場合もまずはHerokuの時と同じようにアダプターの指定をします。そしてproduction.rbの記述は以下のようになります。

config/environments/production.rb

Rails.application.configure do
#省略
 
ActionCable.server.config.disable_request_forgery_protection = true
config.action_cable.url = "ws://[Elastic IP]/cable"
config.action_cable.allowed_request_origins = ['http://[Elastic IP]']
 
#省略
end

 

 次にNginxの設定ファイルであるrails.confを編集します。

ターミナル(EC2内)

sudo vim /etc/nginx/conf.d/rails.conf

 

/etc/nginx/conf.d/rails.conf

upstream app_server {
server unix:/var/www/アプリ名/shared/tmp/sockets/unicorn.sock;
}

server {
listen 80;
server_name Elastic IP;

root /var/www/アプリ名/current/public;

location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
root /var/www/アプリ名/current/public;
}

try_files $uri/index.html $uri @unicorn;

location @unicorn {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://app_server;
}
 
#下記の記述を加える
location /cable {
proxy_pass http://app_server/cable;
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection Upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
 
error_page 500 502 503 504 /500.html;
}

 

保存が完了したら、以下のコマンドを実行します。

sudo systemctl reload nginx
sudo systemctl restart nginx

 commitとpushを行った後bundle exec cap production deployを実行して実装が完了です。

 

 

 

 

 

 

 

Basic認証の導入方法(Heroku)

 Basic認証とは、Webアプリを閲覧できるユーザーを制限するために導入するものです。このBasic認証を導入すると、URLにアクセスした際に、事前に設定したユーザー名とパスワードを入力しなければならなくなります。

 

1. 開発環境で環境変数を設定

2. 環境変数Railsで読み込む

3. 本番環境で環境変数の設定

 

 行うことは以上の3点です。

 

1. 開発環境で環境変数を設定

 まずユーザー名とパスワードを環境変数化します。ターミナルで下記のコマンドを実行していきましょう。MacOSがCatalina以降の場合です。

ターミナル

% vim ~/.zshrc

 「iキー」を押してユーザー名とパスワードを設定します。

export BASIC_AUTH_USER='ユーザー名'
export BASIC_AUTH_PASSWORD='パスワード'

 「escキー」と「:wq」を押して終了し、下記のコマンドを実行します。

% source ~/.zshrc

 

2. 環境変数Railsで読み込む

 コントローラーで下記の記述をします。インデントが整っていないのはブログの仕様上です。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
before_action :basic_auth

private

def basic_auth
authenticate_or_request_with_http_basic do |username, password|
username == ENV['BASIC_AUTH_USER'] && password == ENV['BASIC_AUTH_PASSWORD']
end
end
end

 

3. 本番環境で環境変数の設定

 ターミナルで下記のコマンドを実行します。

ターミナル

% heroku config:set BASIC_AUTH_USER="ユーザー名"
% heroku config:set BASIC_AUTH_PASSWORD="パスワード"

 下記のコマンドで反映できているか確認できます。

% heroku config

 

 下記のコマンドでコミットしてデプロイできます

% git add .
% git commit -m "Basic認証の導入"
% git push heroku master

 

 

 

 おまけではありますが、このBasic認証を導入している場合、コントローラーのテストコードを実行した際にエラーが起きました。

 
before_action :basic_auth if Rails.env.production?
 

  テストコードの際はBasic認証を使わないという記述を加えることで解決しました。

 

 

以上です。

 

 

 

 

 

 

MVCとは何か

プログラミングを学ぶ上で

重要なフレームワークの1つである

MVC」とは何かを

 

プログラミング初心者の目線から

説明していきたいと思います。

 

 

 

MVCとはModel、View、Controllerを

略したものあり

それぞれプログラムの設定を記載する

フォーマットみたいなものです。

 

このMVCの設定をすることで

Webアプリが動くのです。

 

では、それぞれにどのような

設定がされることによって、

僕たちが普段利用している

Webアプリは動いているのでしょうか。

 

 

 

 

僕たちは普段、Webアプリケーションに触れるとき、

どのような情報を扱うでしょうか。

今まさに目にしているのは

文章という文字情報ですし、

 

アプリに登録する際は、

名前や生年月日などのデータを

送信することもあると思います。

 

今示したような、

文字情報に関する設定を記載するのがViewです。

HTMLという文字の設定を

編集するといった作業を行います。

 

また、Webページに事前に記載されている情報ではなく、

それを利用するユーザーが送信するような、

データに関する設定を記述するのがModelです。

 

そしてViewの情報とModelの情報の

橋渡しを担うような役割を

持っているのがControllerです。

 

 

 

 

Modelを工場に例えるなら、

工場で生産したものを店舗まで運搬する

トラックがControllerであり、

実際に消費者のもとに届ける店舗がViewのようなものです。

 

さらにそのトラックを生産してくれる

工場としてRoutingという

フォーマットのようなものもあります。

 

ViewやModelで扱う情報を

Controllerで定義し、

 

さらにそのControllerで扱う情報を生成して、

ブラウザと紐づけてくれる

Routingの設定をして始めて、

Webアプリが動くのです。

 

登録したクレジットカード情報を使って商品を購入する実装

 登録したクレジットカード情報で商品を購入する実装を紹介します。商品を購入できる機能自体は実装済で話を進めていきます。これから伝えるのは、新しくクレジットカード登録画面を設けて、登録している場合はクレジットカード情報を毎度入力しなくても購入ができるようになる実装です。

 この実装を行うことで、クレジットカード情報を登録してもしなくても商品の購入が可能となります。

 

 僕はこの実装にかなり苦戦しました(笑)。Formオブジェクトを用いており、クレジットカード情報と同時に住所や電話番号なども送信できるようにしていることも、苦戦の原因です。

 プログラミング初心者であるため、可読性が高い実装ではないことはご容赦ください。またブログの仕様上、インデントが整っていない場合もあります。

 

 では説明していきたいと思います。いろいろと苦戦したのですが、結果的に行ったことはシンプルでした。以下の5点の流れで説明していきます。

 

1. name属性を区別して実装する

2. クレジットカード登録後はjavascriptを読み込ませない

3. コントローラー内で条件分岐をする

4. バリデーションがかかるような記述を加える

5. 可読性を上げる

 

 

 

1. name属性を区別して実装する

 新しくカード登録画面を作った際に最初に起きた問題が、name属性にありました。僕が行いたかった実装は、クレジットカードを登録してもしなくても商品を購入できるようにすることでした。しかし新しくカード登録画面を作った場合、カード登録画面と、購入画面では、カード情報を入力するフォームのname属性が異なります。

 カード登録画面では、カードナンバーを入力するフォームのname属性が"number"だったのに対し、購入画面では"order_address[number]"となっていたのです。購入画面では、住所や電話番号なども同時に送信できるように、Formオブジェクトを用いているからです。

 そこで、2つのことを試してみました。"order_address[number]"といったname属性をカード登録画面のviewに追記してみることと、もともと作っていたjavascript内で条件分岐をさせることです。条件分岐で行いたかったのは、カード登録画面では"number"、購入画面では"order_address[number]"を取得するといった記述です。しかしどちらもあまりうまくいきませんでした。

 そしてそれ以外の方法として、もともと作っていたjavascriptファイルとは別で、カード登録画面用のjavascriptファイルを新しく作ってみたところ、登録はできるようになりました。

 

もともと作っていたcard.js

const pay = () => {
 
Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
const form = document.getElementById("charge-form");
form.addEventListener("submit", (e) => {
e.preventDefault();

const formResult = document.getElementById("charge-form");
const formData = new FormData(formResult);

const card = {
number: formData.get("order_address[number]"),
cvc: formData.get("order_address[cvc]"),
exp_month: formData.get("order_address[exp_month]"),
exp_year: `20${formData.get("order_address[exp_year]")}`,
};

Payjp.createToken(card, (status, response) => {

if (status == 200) {
const token = response.id;
const renderDom = document.getElementById("charge-form");
const tokenObj = `<input value=${token} name='token' type="hidden"> `;
renderDom.insertAdjacentHTML("beforeend", tokenObj);
}
 
document.getElementById("card-number").removeAttribute("name");
document.getElementById("card-cvc").removeAttribute("name");
document.getElementById("card-exp-month").removeAttribute("name");
document.getElementById("card-exp-year").removeAttribute("name");

document.getElementById("charge-form").submit();
});

});
};

window.addEventListener("load", pay);

 

新たに作成したcard_save.js

const save = () => {
 
Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
const form = document.getElementById("charge");
form.addEventListener("submit", (e) => {
e.preventDefault();
 
const formResult = document.getElementById("charge");
const formData = new FormData(formResult);


const card = {
number: formData.get("number"),
cvc: formData.get("cvc"),
exp_month: formData.get("exp_month"),
exp_year: `20${formData.get("exp_year")}`,
};

Payjp.createToken(card, (status, response) => {

if (status == 200) {
const token = response.id;
const renderDom = document.getElementById("charge");
const tokenObj = `<input value=${token} name='card_token' type="hidden"> `;
renderDom.insertAdjacentHTML("beforeend", tokenObj);
}
 
document.getElementById("card-number").removeAttribute("name");
document.getElementById("card-cvc").removeAttribute("name");
document.getElementById("card-exp-month").removeAttribute("name");
document.getElementById("card-exp-year").removeAttribute("name");

document.getElementById("charge").submit();
});

});
};

window.addEventListener("load", save);

 

 新たにjavascriptファイルを作成したときに、読み込みの記述を忘れてしまうという初歩的なミスもしてしまったので気をつけてください。console.logやdebuggerを地道に試していけばどこから読み取れていないかを確認できます。

app/javascript/packs/application.js

 
require("../card_save")
 

 

 また、もともと実装していた購入処理はorders_controllerに記述していますが、新しく追加したカード登録の処理では、cards_controllerを用いています。購入画面からクレジットカード登録のリンクに飛んで、登録ボタンを押したら購入画面に戻るような設定にしているため、このような記述となっています。

app/controllers/cards_controller.rb

class CardsController < ApplicationController
def new
session[:previous_url] = request.referer
end

def create
Payjp.api_key = ENV['PAYJP_SECRET_KEY']
customer = Payjp::Customer.create(
description: 'test',
card: params[:card_token]
)

card = Card.new(
card_token: params[:card_token],
customer_token: customer.id,
user_id: current_user.id
)

if card.save
redirect_to session[:previous_url]
else
render :new
end
end

def session_clear
session[:previous_url].clear
end
end

 

2. クレジットカード登録後はjavascriptを読み込ませない

 これでクレジットカード登録が完了しました。登録が完了すると登録したカード情報が購入画面に表示されるようになっています。

app/views/orders/index.html.erb

<% if @card.present? %>
<div class='credit-card-form'>
<h1 class='info-input-haedline'>
クレジットカード情報入力
</h1>
<div class="form-group">
<div class='form-text-wrap'>
<label class="form-text">カード情報</label>
<span class="indispensable">必須</span>
</div>
<%= "**** **** **** " + @card[:last4] %>
<div class='available-card'>
<%= image_tag 'card-visa.gif', class: 'card-logo'%>
<%= image_tag 'card-mastercard.gif', class: 'card-logo'%>
<%= image_tag 'card-jcb.gif', class: 'card-logo'%>
<%= image_tag 'card-amex.gif', class: 'card-logo'%>
</div>
</div>
<div class="form-group">
<div class='form-text-wrap'>
<label class="form-text">有効期限</label>
<span class="indispensable">必須</span>
</div>
<div class='input-expiration-date-wrap'>
<%= @card[:exp_month] %>
<p></p>
<%= @card[:exp_year] %>
<p></p>
</div>
</div>
</div>
<% else %>

 

 下記の記述が使えるのは、orders_controllerのindexでカード情報の定義をしているからです。

 
<% if @card.present? %>
 

 

 それではこの状態で購入ボタンを押してみます。すると、そもそも購入ボタンが押せない問題が発生しました。どうやらjavascriptに問題がありそうです。card.jsとcard_save.jsに記述している「e.prevent.Default()」が動いていることが原因だと考えられます。これを動かないようにすれば解決できそうです。

 購入の処理自体はコントローラーで行うため、カードの登録が完了している場合は、card.jsとcard_save.jsの両方を読み込ませないようにする必要があります。

 そこでカードを登録している場合のみ、"save_card"というidを付け加えました。

app/views/orders/index.html.erb

<% if @card.present? %>
<div class='credit-card-form', id="save_card">
<h1 class='info-input-haedline'>
クレジットカード情報入力
</h1>

 そしてcard.jsとcard_save.jsの両方に、"save_card"というidを取得したときは、それ以降の記述を読み込ませないようにしました。

app/javascript/card.js

const pay = () => {
const saveCard = document.getElementById("save_card")
if (document.getElementById("save_card")) {
return saveCard;
};

app/javascript/card_save.js

const save = () => {
const saveCard = document.getElementById("save_card")
if (document.getElementById("save_card")) {
return saveCard;
};

 これで購入ボタンは押せるようになります。

 

3. コントローラー内で条件分岐をする

 しかし購入ボタンが押せるようになったところで、購入ができるようになったわけではありません。カードを登録している場合と登録していない場合で購入の処理を分ける必要があります。登録している場合もしていない場合も同じ購入画面で処理を行うので、orders_controllerを用います。

 createアクションでprivateメソッド内のpay_itemを呼び出すようにしています。その時にpay_itemの処理をカード登録している場合としていない場合で条件分岐させます。

app/controllers/orders_controller.rbのprivateメソッド内

def pay_item
if @card.present?
Payjp.api_key = ENV['PAYJP_SECRET_KEY']
customer_token = current_user.card.customer_token
Payjp::Charge.create(
amount: @item.price,
customer: customer_token,
currency: 'jpy'
)
else
Payjp.api_key = ENV['PAYJP_SECRET_KEY']
Payjp::Charge.create(
amount: @item.price,
card: order_params[:token],
currency: 'jpy'
)
end

 

 またindexアクションでもcreateアクションでもカード情報の定義をする必要があったため、privateメソッド内にsave_cardとしてカード情報の定義をまとめました。indexアクションでsave_cardを呼び出さなければ、登録したカード情報が購入画面に表示されなくなります。このsave_cardをindexアクションとcreateアクションの両方で呼び出すようにしました。

app/controllers/orders_controller.rbのprivateメソッド内

def save_card
Payjp.api_key = ENV['PAYJP_SECRET_KEY']
card = Card.find_by(user_id: current_user.id)

if card.present?
customer = Payjp::Customer.retrieve(card.customer_token)
@card = customer.cards.first
end
end

 

 これで後はcreateアクションでsave_cardとpay_itemを呼び出すような記述をすれば、登録したカード情報で購入すること自体は完了します。createアクション内の記述は後述します。

 

4. バリデーションがかかるような記述を加える。

 ところがこれでもまだ問題があったのです。今回の実装ではorder_address.rbというFormオブジェクトを用いていたことに問題の原因があります。下記のコードはページに収めるためにやや変更したものです。

app/models/order_address.rb

class OrderAddress
include ActiveModel::Model
attr_accessor :item_id, :user_id, :postal_code, :prefecture_id, :city, :address,
:building, :phone_number, :token

with_options presence: true do
validates :item_id
validates :user_id
validates :postal_code, format: { with: /\A[0-9]{3}-[0-9]{4}\z/}
validates :city
validates :address
validates :phone_number, numericality: { only_integer: true }
validates :token
end

validates :prefecture_id, numericality: { other_than: 1, message: "can't be blank" }
validates :phone_number, length: { minimum: 10, maximum: 11 }

def save
order = Order.create(item_id: item_id, user_id: user_id)

Address.create(postal_code: postal_code, prefecture_id: prefecture_id, city: city,
address: address, building: building,
phone_number: phone_number, order_id: order.id, user_id: user_id, item_id: item_id)
end
end

 Formオブジェクトを用いた場合は、createアクション内でバリデーションの記述をしなければバリデーションがかかりません。

 
if @order_address.valid?
 

 save_cardとpay_itemを呼び出せば登録したカード情報で購入すること自体は可能です。しかしバリデーションの記述をしていない場合は、登録に失敗した時にエラーが起きます。

 とはいえ、

if @order_address.valid ?という記述をして購入ボタンを押すと、token can't be blankというバリデーションがかかるのです。

 カード登録をしていない場合は、購入画面でカード情報を入力するため、"order_address[token]"というように、tokenがorder_addressの中に格納されるため、購入することができます。しかしカード登録画面ですでにカード情報を登録して購入画面に戻った場合は、購入画面でカード情報を入力しません。そのためorder_addressの中にtokenだけが格納されていない状態になってしまうのです。

 

 そこで2つのことを試してみました。まずカードを登録しているときだけ、tokenのバリデーションをかけないように下記の記述をしてみました。

 
if @order_address.where.not(token: params[:token]).valid?
 

 ところがこれではwhereが定義されていないというエラーが起きます。そして今度はorder_addressモデル内でifオプションを用いて、カード登録しているときだけtokenのバリデーションをかけないようにしようとしましたがうまくいきませんでした。

 あと1歩なのにどうしようと悩んだ結果、「付け加えてみよう」と考え、カード登録をしているときだけ、下記の記述を加えました。

 
@order_address.token = current_user.card.customer_token
 

 結果的にこれでうまくいきました。createアクション内の記述は以下のようになりました。カードを登録している場合はtokenの記述を書き加えた上でバリデーションをかけるようにしました。インデントが整っていないので見づらいと思いますが、許してください。

def create
@order_address = OrderAddress.new(order_params)
if save_card
@order_address.token = current_user.card.customer_token
if @order_address.valid?
pay_item
@order_address.save
redirect_to root_path
else
render 'orders/index'
end
elsif @order_address.valid?
pay_item
@order_address.save
redirect_to root_path
else
render 'orders/index'
end
end

 

 

5. 可読性を上げる

 追記です。さすがに同じ記述を何度も繰り返し書いていて読みづらいので書き直してみました。インデントが整っていなくて読みづらいのは申し訳ありません。ブログの仕様上です。

app/controllers/orders_controller.rbbの全体

class OrdersController < ApplicationController
before_action :authenticate_user!
before_action :set_order
before_action :move_to_index

def index
@order = Order.new
@order_address = OrderAddress.new
Payjp.api_key = ENV['PAYJP_SECRET_KEY']
card = Card.find_by(user_id: current_user.id)

if card.present?
customer = Payjp::Customer.retrieve(card.customer_token)
@card = customer.cards.first
end
end

def create
@order_address = OrderAddress.new(order_params)
Payjp.api_key = ENV['PAYJP_SECRET_KEY']
card = Card.find_by(user_id: current_user.id)
if card.present?
customer = Payjp::Customer.retrieve(card.customer_token)
@card = customer.cards.first
@order_address.token = current_user.card.customer_token
end

if @order_address.valid?
pay_item
@order_address.save
redirect_to root_path
else
render 'orders/index'
end
end

private

def order_params
params.require(:order_address).permit(:postal_code, :prefecture_id, :city, :address,
:building, :phone_number, :order_id).merge(
user_id: current_user.id, item_id: params[:item_id], token: params[:token]
)
end

def set_order
@item = Item.find(params[:item_id])
end

def move_to_index
redirect_to root_path if current_user.id == @item.user.id || @item.order.present?
end

def pay_item
if @card.present?
Payjp.api_key = ENV['PAYJP_SECRET_KEY']
customer_token = current_user.card.customer_token
Payjp::Charge.create(
amount: @item.price,
customer: customer_token,
currency: 'jpy'
)
else
Payjp.api_key = ENV['PAYJP_SECRET_KEY']
Payjp::Charge.create(
amount: @item.price,
card: order_params[:token],
currency: 'jpy'
)
end
end
end

 if card.present?を繰り返し使っていたり、条件分岐の中に条件分岐を書いたりして読みづらくなっていたため、save_cardをprivateメソッド内で定義して呼び出すのをやめて書き直しました。

 
Payjp.api_key = ENV['PAYJP_SECRET_KEY']
 

 この記述に関しては何度も繰り返して書いているのですが、これはまとめて書いて呼び出すようにするとエラーが起きてしまったからです。

 

 以上でカード情報を登録してもしなくても商品を購入できる実装の完了です。全体的にネーミングセンスが悪く、可読性ももっと高められるはずです。しかし1箇所を修正すると全体を修正する必要性が生じたり、確認作業が増えたりするので、次の実装に生かしていこうと考えています。

 

 

 

 

 

ユーザー管理機能の実装方法

 Ruby on Railsで作成しているアプリケーションでユーザー管理機能を実装する方法を紹介したいと思います。deviseというgemライブラリを用いて実装していきます。

 

 流れは以下の通りです。

1. deviseの導入

2. deviseの設定ファイルを作成

3. deviseのユーザーモデルを作成

4. テーブルの作成

5. deviseのビューファイルの作成

6. 後でカラムを追加する場合

7. ストロングパラメーターの使用

8. その他

 

1. deviseの導入

 まずはdeviseをインストールします。Gemfileの最後の行にdeviseの記述を加えて、ターミナルでbundle installをしましょう。

Gemfile

gem 'devise'

ターミナル

% bundle install

 

 

2. deviseの設定ファイルを作成

 deviseを使用するために、devise専用のコマンドで設定ファイルを作成しましょう。

ターミナル

% rails g devise:install

 

3. deviseのユーザーモデルを作成

 ユーザー情報を管理するためのモデルを作成しましょう。

ターミナル

% rails g devise user

 

4. テーブルの作成

 nicknameなどの追加したいカラムがある場合は、マイグレーションファイルに追記しましょう。

db/migrate/20xxxxxxxxx_devise_create_users.rb

t.string :nickname,           null: false
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""

 テーブル設計が確認できたらマイグレーションを実行しましょう。

ターミナル

% rails db:migrate

 

5. deviseのビューファイルの作成

 deviseのビューファイルを生成しましょう。カラムを追加している場合はビューファイルを編集しましょう。

ターミナル

% rails g devise:views

 

6. 後でカラムを追加する場合

 もし後になってマイグレーションファイルに新しくカラムを追加したくなった場合は下記のコマンドを実行しましょう。firstnameカラムを追加するなら下記のようになります。

% rails g migration AddFirstnameToUsers firstname:string

 rails db:rollbackをした後にカラムを追記して再度rails db:migrateを実行する方法もあります。

 

7. ストロングパラメーターの使用

 deviseの処理を行うコントローラーはGem内に記述されているため、編集することができません。そのため、すべてのコントローラーが継承しているファイルであるapplication_controller.rbファイルにストロングパラメーターを定義することで、処理を読み込ませます。deviseが提供しているdevise_parameter_sanitizerメソッドを使って、追加したカラムの処理を許可させましょう。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?

private

 
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up,
keys: [:nickname, :firstname])
end
end

 

 以上でユーザー管理機能の実装は完了です。

 

 

 8. その他

 ユーザー管理機能の実装時に使える記述をいくつか紹介します。

 下記のメソッドは未ログインユーザーをログインページに転送させます。

controller

 
  before_action :authenticate_user!
 

 

 未ログインユーザーを特定のページにアクセスできないようにしたいときは、下記のような記述ができます。リダイレクトという仕組みを使ってトップページに遷移させることができます。

controller

 
before_action :move_to_index, except: [:index, :show, :search]
 
def move_to_index
unless user_signed_in?
redirect_to action: :index
end
end

 

 ログインの有無で処理を変えたい場合は下記の記述をします。

view

 
<% if user_signed_in? %>
処理を記述
<% end %>
 

 

 

以上です!

 

 

 

 

 

Ruby on Railsを用いたアプリ作成方法

 Ruby on Railsを用いたアプリ作成方法を紹介します。Ruby on Railsは手っ取り早くアプリの全体像を掴む上で効果的です。

 これから環境構築でRailsのインストールを終えていることを前提に話を進めていきます。

 

 アプリ作成の手順は下記の通りです。

1. Railsアプリケーションの雛形作成

2. ディレクトリの移動

3. データベースの作成

4. Sequel Proのダウンロード

5. ローカルサーバーの起動

 

1. Railsアプリケーションの雛形作成

 ターミナルで下記のコマンドを実行するとアプリの雛形が作成されます。

% rails new 作成したいアプリケーション名

 またアプリの雛形を作成する際は、バージョンとオプションを指定します。バージョン6.0.0を指定する場合は下記のようになります。

% rails _6.0.0_ new アプリケーション名 -オプション名

 またオプションとしてデータベース管理システムMySQLを指定する場合は下記のようになります。

% rails _6.0.0_ new アプリケーション名 -d mysql

 

2. ディレクトリの移動

 change directoryの略であるcdコマンドを用いて作成したアプリケーションのディレクトリに移動できます。

% cd アプリケーション名

 

3. データベースの作成

 Railsではデータを元に動作や表示を変更させます。作成したアプリケーションのディレクトリに移動した上で、下記のコマンドでデータベースを作成しましょう。

% rails db:create

 

4. Sequel Proのダウンロード

 Sequel Proをダウンロードすると、作成したデータベースの中身を確認できるようになります。下記のリンクからダウンロードできます。

Downloads

 

 

5. ローカルサーバーの起動

 rails sコマンドでサーバーの起動を行うことができます。サーバーを停止する際は、control + cを同時に押します。

% rails s

 ローカルサーバーを起動し、ブラウザで下記のリンクにアクセスすると、Railsアプリケーションのデフォルト画面が表示されます。アプリケーションの中身を記述していくことで、画面の表示が変えていくことができます。

http://localhost:3000

 

 

以上です!

お疲れ様でした!