deviseを用いたユーザーまわりの機能の実装②

ログイン・サインアップ画面のビューの変更

deviseを使用してログイン機能を実装すると自動的にログイン画面とサインアップ画面が生成される。 ただしこれはとてもシンプルな見た目になっているため、UIを整えていくことが多い。

手順

①devise用のビューファイルを作成 ②ログイン・サインアップ画面のビューファイルの編集

①devise用のビューファイルを作成する

deviseでログイン機能を実装すると、ログイン・サインアップ画面は自動的に生成されるがビューファイルとしては生成されない。 これは、deviseのGem内に存在するビューファイルを読み込んでいるためである。 よって、ビューファイルに変更を加えるためには、deviseのコマンドを利用してビューファイルを生成する必要がある。

・下記のコマンドを実行し、devise用のビューファイルを生成する。 ターミナル

  $ rails g devise:views

これにより、app/views/devise以下のディレクトリにビューファイル各種が新規作成される。

②ログイン・サインアップ画面のビューファイルの編集

①の作業でdevise用のビューファイルを生成することができ他ので、次は生成されたビューファイルを編集してレイアウトを変更していく。

サインアップ画面は「app/views/devise/registrations/new.html.erb」ログイン画面のビューは「app/views/devise/sessions/new.html.erb」というビューファイルが対応しているため、この2つのファイルに変更を加え、作りたい見た目を作成していく。

サインアップ時の登録情報を増やす

今回は、サインアップ時にニックネームを登録できるようにする(上記までの作業で、メールアドレスとパスワードの2つが登録できている状態のはず)。

手順

①ユーザーテーブルにカラムを追加する。 ②登録画面のビューを編集する。 ③ストロングパラメーターを編集する。

①ユーザーテーブルにカラムを追加する

ニックネームを登録するために、usersテーブルにニックネームを保存するためのカラムを追加する。 追加したカラムに、サインアップ時に登録するニックネームを保存することになる。

・カラムを追加するマイグレーションファイルの作成 カラムを追加するマイグレーションファイルを作成するためには、下記コマンドを実行する。

$ rails g migration Addカラム名To追加先テーブル名 追加するカラム名:型

例えば、userの自己紹介を保存するためのカラム、introductionカラムをusersテーブルにtext型で追加したいとすると、以下の例のように書くことになる。

例) ターミナル

  $ rails g migration AddIntroductionToUsers introduction:text
  # usersテーブルにintroductionカラムをtext型で追加するマイグレーションファイルの作成

この時、「Add」「Introduction」「To」「Users」と、単語の頭文字が大文字になっていることに注目。 表記方法について以下に記す。

スネークケースとキャメルケース

プログラムが単語の区切りを認識する際、2通りの方法がある。その2つが「スネークケース、キャメルケース」である。 スネークケースは、「sample_ai_ueo」のように「 _ 」で単語を区切る方法。 キャメルケースは、「SampleAiUeo」のように単語の頭文字を大文字にすることで単語を区切る方法。

②登録画面のビューを編集する

これでDB側の準備は整ったので、続いてユーザーの新規登録画面にニックネームを登録するためのフォームを追加する。 ユーザーの新規登録画面のビューは、app/views/devise/registrations/new.html.erb。 今回は入力文字数を制限するオプションもつけているが、必須ではないので無視しても構わない。

text_field:maxlengthオプション

maxlengthは、text_fieldというinputタグを生成するヘルパーメソッドにつけることができるオプション。

app/views/devise/registrations/new.html.erb

  <div class="field">
    <%= f.text_field :nickname, autofocus: true, maxlength: "6" %>
  </div>

例えば本コードの場合、生成されたフォームに7文字以上入力すると、エンターキーを押した瞬間に6文字までカットされる。

③ストロングパラメーターを編集する

サインアップ時に入力する情報はパラメーターとしてサーバーに送信されるため、通常のリクエストの場合は、コントローラに記述してあるストロングパラメーターで受け取れるパラメーターを制限することになる。 しかし、deviseでログイン機能を実装した場合のパラメーターの受け取り方は通常とは異なる。ログイン時に送られてくるパラメーターを制限するストロングパラメーターは、deviseのGem内に記述されているため編集することはできないのである。 ではどうするかというと、今回追加したニックネームを受け取れるようにするにはdeviseが提供しているconfigure_permitted_parametersメソッドを利用する。

configure_permitted_parametersメソッド

deviseでは初期状態でサインアップ時にメールアドレスとパスワードのみを受け取るようにストロングパラメーターが設定してあるため、追加したキーのパラメーターは許可されていない。追加のパラメーターを許可したい場合は、application_controllerにおいてbefore_actionにconfigure_permitted_parametersメソッドを設定する必要がある。

例)

  class ApplicationController < ActionController::Base
    # Prevent CSRF attacks by raising an exception.
    # For APIs, you may want to use :null_session instead.
    protect_from_forgery with: :exception
    before_action :configure_permitted_parameters, if: :devise_controller?

    def configure_permitted_parameters
      devise_parameter_sanitizer.permit(:ストロングパラメーターを追加したいアクション名, keys: [:追加するキー])
    end
  end

今回は、sign_upアクションに対してnicknameというキーのパラメーターを追加で許可する。

・app/controllers/application_controllerを以下のように編集する。 app/controllers/application_controller.rb

  class ApplicationController < ActionController::Base
    # Prevent CSRF attacks by raising an exception.
    # For APIs, you may want to use :null_session instead.
    protect_from_forgery with: :exception
    before_action :configure_permitted_parameters, if: :devise_controller?

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

上記の一連の作業により、サインアップ時にニックネームを登録することができるようになる。

deviseを用いたユーザーまわりの機能の実装

多くのウェブサイトにはユーザーログイン機能がある。 この機能はサインアップしたユーザーに対してアカウントを発行し、そのアカウントで情報を管理することができる機能である。 Railsの場合、ログイン機能は「devise」というGemを使用することで簡単に実装することができる。

devise

deviseは、ログイン機能を簡単に作成することができるGem。ログイン機能をGem無しで実装するのは非常に大変だが、このGemを使うことで比較的簡単に実装することができる。

deviseのインストール

・Gemfileの最終行に以下のように追記 Gemfile

〜
  gem 'devise'  # 最終行に追記してください

・bundle installを実行 ターミナル

  $ pwd
  #現在のディレクトリが~/projects/pictweetであることを確認
  $ bundle install
  #bundle install の実行

本記事では、ログイン機能を実装することで、ログインしたユーザーのみが投稿を行えるようにするところまで説明する。 閲覧に関してはログインにかかわらずできるようにします。

作業内容

(①Gemをインストールしてサーバーを立ち上げ直す) ②コマンドを利用してdeviseの設定ファイルを作成する ③コマンドを利用してUsersモデルを作成する ④未ログイン時にはログインと新規登録ボタンを表示する ⑤コントローラにリダイレクトを設定する

①Gemをインストールしてサーバーを立ち上げ直す ログイン機能を実装する際には「devise」というGemをインストールして使用する。またGemをインストールした後はrails sをし直しサーバーを立ち上げ直す必要がある。これはインストールしたGemが反映されるタイミングがサーバーを立てるときだから。

②コマンドを利用してdeviseの設定ファイルを作成する deviseを使用するためには、Gemのインストールに加えてdevise専用のコマンドを利用して設定ファイルを作成する必要がある。 まず、ターミナルから下記のコマンドを実行する。 ターミナル

  $ rails g devise:install
  # deviseの設定ファイルを作成

これにより、以下のファイルが新規作成される

config/initializers/devise.rb config/locales/devise.en.yml

③コマンドを利用してUserモデルを作成する deviseを利用する際にはアカウントを作成するためのUserモデルを新しく作成する。作成には通常のモデルの作成方法ではなく、deviseのモデルの作成用コマンドを使用するので注意。

rails g deviseコマンド

deviseで、ログイン機能をつける概念のモデルを作成する際に利用するコマンド。 モデルに加えて、ログイン機能のために必要なカラムが追加されるマイグレーションファイルなどが生成される。

次に、下記の指示に従い、ログイン機能を持つuserクラスを作成する。

rails g deviseコマンドでuserモデルを作成してください ターミナル

  $ rails g devise user
  # deviseコマンドでモデルを作成

これにより以下のファイルが新規作成される

app/models/user.rb db/migrate/2014XXXXXXXXXX_devise_create_users.rb test/fixtures/users.yml test/models/user_test.rb

また、config/routes.rbに以下の様な記述が自動的に追記される。

config/routes.rb

Rails.application.routes.draw do
  devise_for :users
#以下略
devise_for

devise_forはログインまわりに必要なルーティングを一気に生成してくれるdeviseのヘルパーメソッド。

例えばdevise_for :usersの記述により、以下のように「ログイン・新規登録」で必要なルーティングが生成される。 また、後ほど登場するcurrent_userやuser_signed_in?などのヘルパーメソッドも利用できるようになる。

ターミナル

                  Prefix Verb   URI Pattern                    Controller#Action
                    root GET    /                              tweets#index
              tweets_new POST   /tweets/new(.:format)          tweets#new
                  tweets POST   /tweets(.:format)              tweets#create
                         GET    /users/:user_id(.:format)      users#show
                         DELETE /tweets/:id(.:format)          tweets#destroy
                         GET    /tweets/:id/edit(.:format)     tweets#edit
                         PATCH  /tweets/:id(.:format)          tweets#update
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
           user_password POST   /users/password(.:format)      devise/passwords#create
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy

・rake db:migrateを実行 ターミナル

  $ rake db:migrate
  # 作成されたマイグレーションファイルを実行

user.rbに「:trackable」の記述がある場合、「:trackable」を削除してください。

app/models/user.rb(修正前)

lass User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
 devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :trackable, :validatable
end

         ↓

app/models/user.rb(修正後)

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
 devise :database_authenticatable, :registerable,
      :recoverable, :rememberable, :validatable
end

④未ログイン時にはログインと新規登録ボタンを表示する 誰かが未ログイン時にツイート一覧画面を表示した際に、ヘッダーに投稿ボタンの代わりにログインと新規登録ボタンを表示するようにする。

【補足】Rubyタグ

<%=と%>で囲まれた部分をRubyタグと言う。Rubyタグは拡張子が「erb」のビューファイルで使用することができる。Rubyタグを使用して記述されたコードはビューファイルが読み込まれる際にHTMLコードとなって読み込まれる。また-%>のように閉じるタグに-をつける形もある。このようにすることで、余計な改行を取り除くことができる。

※<%= %>と<% %>の違いは、Rubyタグに囲まれた処理の返り値をビューに出力するか、しないかという点。

例1) <%= tweet.text %>

この場合、計算結果を出力したいので<%= %>を使用。

例2) <% if user.name == "たなかたろう" %>

この場合、条件分岐の処理に使いたいだけで、user_signed_in? の返り値は特に出力する必要はないため<% %>を使用。

link_toメソッド

link_toはRubyタグの中で使用することができるメソッド。このメソッドは引数を指定することで様々なリンクを生成する。 通常HTMLコード内でリンクを生成する際にはaタグを使用する。link_toメソッドを使って記述を行うと、HTMLコードが読み込まれる際にaタグに変換されるため、サイトを表示した際にはaタグと同様に、リンクとして表示される。今回の実装で追加するログインや新規登録ボタンもこのメソッドを利用して生成する。

例) sample.html.erb

  <%= link_to '作品一覧へ', '/products' %>
  # link_toメソッドを使ってリンクを生成

また、htmlの要素に指定できるclassなどの属性は、以下の例のように続けてclass: 'sample'などと書くことで付与することができる。

sample.html.erb

  <%= link_to '作品一覧へ', '/products', class: 'sample' %>
  # 作成したaタグに`class="sample"`属性を付与

link_toメソッドがビューファイルとして読み込まれる際には、以下の様なHTMLコードになる。

例) sample.html.erb

  <a class="sample" href="/products">作品一覧へ</a>
user_signed_in?

deviseでログイン機能を実装すると、user_signed_in?というメソッドを使用することができる。 これは、ユーザーがサインインしているかどうか検証するメソッド。サインインしている場合にはtrueを返し、サインインしていない場合にはfalseを返す。

例) sample.html.erb

  <% if user_signed_in? %>
    # ユーザーがサインインしている場合に実行する処理
  <% end %>

user_signed_in?が返す値は最終的にtrueかfalseになるので、上記の例のようにif文または、unless文とともに使用する。

prefix

Prefix(プレフィックス)とは、ルーティングのパスが入る変数のこと。コントローラやビューなどで呼び出すことで、prefixに入っているパスやURL情報を取得できるようになる。Prefixは、routes.rbの各リクエストにオプションとして設定しますが、記述によっては自動的に作成される場合もある。 これを確認するには、ターミナルからrake routesコマンドを実行するといい。 実行例は、以下のような表記。

例)ターミナル

$rake routes
#実行結果

                  Prefix Verb   URI Pattern                    Controller#Action
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
           user_password POST   /users/password(.:format)      devise/passwords#create
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy
                    root GET    /                              tweets#index
              tweets_new GET    /tweets/new(.:format)          tweets#new
                  tweets POST   /tweets(.:format)              tweets#create
                         GET    /users/:id(.:format)           users#show
                         DELETE /tweets/:id(.:format)          tweets#destroy
                         GET    /tweets/:id/edit(.:format)     tweets#edit
                         PATCH  /tweets/:id(.:format)          tweets#update

上記の表示でわかる通り、Prefixが設定されている場合は表の一番左に示される。

deviseを導入した場合、ユーザーの新規登録やログインに関するprefixは予め決まっており、以下のようになる。

例)deviseによって設定されるprefixの一部 | リクエスト | prefix | パス | | ----- | ----- | ----- | | devise/sessions#new | new_user_session | /users/sign_in | | devise/sessions#create | user_session | /users/sign_in | | devise/sessions#destroy | destroy_user_session | /users/sign_out |

実際にprefixを利用する場合は、new_user_session_pathのように、最後に_pathとつける必要がある。

⑤コントローラにリダイレクトを設定する 今のままだと、未ログインユーザーがツイートの一覧画面を表示しても投稿ボタンが表示されなくなっても、未ログインユーザーが直接/tweets/newというパスにアクセスすることで投稿ができてしまう状態である。 そこで、未ログインユーザーが投稿画面など直接アクセスしてきた際にはルートパスに遷移するように設定を行う。

unless文

ここまで条件分岐にはif文を使用してきたが、似た制御構造を持つものとしてunless文がある。unless文は条件式が偽(false)の場合の処理を記述するのに使われる。今回はユーザーがログインしていない場合の条件分岐にunless文を使用してみる。基本的なunless文の書き方は以下の通り。

例)

  unless 条件式
    # 条件式が偽(false)のときに実行する処理
  end

また条件分岐の中が一行で記述できる場合は、if/unless文は以下のように一行で記述することができる。 記述が簡単で見やすくなるので、一行で記述できる場合は一行で記述すると良い。

例)

  puts 'ログインをしてください' unless user_signed_in?
  # 以下と同義

  unless user_signed_in?
    puts 'ログインをしてください'
  end
redirect_toメソッド

Railsでは通常、アクション内の処理が終了すると自動的にアクション名と同名のビューが表示される。 ただしredirect_toメソッドをアクション内で利用すると、そこからさらに別のアクションを実行したり、ビューに遷移させたりできる。

引数にはaction: :indexという形で、キーがaction:バリューが:indexであるハッシュを指定する。このようにバリューにはアクションの名前のシンボル型を利用する。丁寧に書くのであれば{ action: :index }となるが、Railsの内部では特別にハッシュの括弧{}を省略することができる。

今回はindexアクションを実行させるために、redirect_to action: :indexとする。

例)app/controllers/products_controller.rb

  class ProductsController < ApplicationController

    def index
      @products = Product.page(params[:page]).per(5).order("created_at DESC")
    end

    private
    def move_to_index
      redirect_to action: :index
      # indexアクションを強制的に実行する
    end
  end
before_action

Railsではコントローラでbefore_action :メソッド名と記述することで、コントローラのアクションが実行される前にそのメソッドを実行することができる。 また、オプションonlyやexceptを使うことにより、before_actionを実行することをアクションごとに制限をかけることができる。

例)indexアクション以外でbefore_actionを実行したい場合 app/controllers/products_controller.rb ``` class ProductsController < ApplicationController

before_action :hoge, except: :index
# indexアクション以外が実行される前にhogeが実行される。

def index
  @products = Product.page(params[:page]).per(5).order("created_at DESC")
end

private
def hoge
end

end

以上でログイン機能の実装は完了している。
ただ、あくまで最低限のものなので、ビューや細かい登録内容の変更を行う必要がある。

RubyのGemである「kaminari」について

ページネーションを実装する際には「kaminari」というGemをインストールして使用します。またGemをインストールした後はrails sをし直し、サーバーを立ち上げ直す必要があります。インストールしたGemが反映されるタイミングがサーバーを立てるときだからです。

kaminari

kaminariはRubyのGemの一種。 このGemのインストールにより、簡単にページネーションを実装することができる。

kaminariインストール手順

①Gemfileに下記の記述を追記

Gemfile

~
gem 'kaminari'  # 最終行に追記してください

②bundle installを実行 ターミナル

  $ pwd
  #現在のディレクトリを確認
  $ bundle install
  #bundle install の実行

(③ローカルサーバーを再起動) ターミナル

  $ rails s

④コントローラファイルの編集 例えば、1ページに表示される情報を5件にする場合、コントローラからビューに送るツイート情報も5件のみになる。その数を制御するために、kaminariをインストールすると使える2つのメソッドを利用する。

kaminari:pageメソッド

kaminariを導入すると、モデルクラスにpageメソッドが定義される。これにより、ページネーションにおけるページ数を指定することができる。 ビューのリクエストの際paramsの中にpageというキーが追加され、その値がビューで指定したページ番号となる。そのため、pageの引数はparams[:page]となる。

kaminari:perメソッド

perメソッドもpageメソッドと同様、kaminariというgemをインストールすることで利用できるメソッド。1ページあたりに表示する件数を指定する。 perメソッドに引数として渡した数字が、ページネーションが実装されたビューで1ページあたりに表示する件数になる。

上記2つのメソッドは、以下のように使われる。

app/controllers/products_controller.rb

  変数名 = クラスを利用して取得したレコードのインスタンス.page(params[:page]).per(ここに1ページで表示したい件数を入力)

例えば、コントローラでproductsテーブルのレコードを定義し、indexアクションのビューに渡してページネーションを実装したい場合は下記のように表記する。

def index
  @products = Product.order("created_at DESC").page(params[:page]).per(5)
end

⑤ビューファイルの編集 ページネーションを実装する際にはコントローラだけでなくビューにも編集を加える必要がある。

kaminari:paginateメソッド

ページネーションのリンクを表示したいときに使用するメソッド。paginateメソッドの引数は、コントローラで定義した変数を指定する。

編集例は下記のように、<%= paginate(@products) %>を追記する。 app/views/products/index.html.erb

  <div class="contents row" >
    <% @products.each do |product| %>
      <div class="content_post" style="background-image: url(<%= product.image %>);">
        <%= simple_format(product.text) %>
        <span class="name"><%= product.name %></span>
      </div>
    <% end %>
  <%= paginate(@products) %>
</div>

※このとき、データベースのproductsテーブルにレコードが6個以上ないとページネーションは表示されないので注意。

データベース設計について

モデルとテーブル

Twitterのように、ユーザーが投稿した情報をHPのview上に反映させるサイトのことを動的サイトというが、これを作成するには、情報を保存する、またそれを引き出してviewに反映する仕組みが必要となる。その際に必要な考え方として「モデル」と「テーブル」がある。

まず、用語について簡単に説明を記述する。

テーブル

テーブルは、表の形式をした収納場所。データベースに複数存在し、それぞれのテーブルに情報を整理して保存する。

レコードとカラム

テーブルの横1行のことをレコード、縦1列のことをカラムという。

テーブルの条件

RailsによるWebアプリケーション用データの保存場所であるテーブルには、2つの特徴がある。 ①あるテーブル内のレコードを特定するための値として、idというカラムが与えられる。通常、idカラムには整数が入る。 ②行列によって区切られた1マスに入る値は1つでないといけない。

DB設計

アプリケーション開発においてほとんどのデータはデータベース内に保持される。アプリケーションは、言うなれば データの流通機構であり、どのようなデータをどういう形式で設計するかによって、品質は大きく左右される。 アプリケーション開発において、必要なテーブルやアプリケーションが快適に動作するように取り出しやすいデータベース構造をあらかじめ設計しておくことが必要となる。

※これは近年のソフトウェア開発での主流の考え方で、 DOA と言います。

DOA

DOAとは、 データ中心アプローチ(Data Oriented Approach) という考え方のこと。これは、プログラムよりも前にデータ設計から始める方法論のことを言う。DOAはサービス開発が効率的になるため近年主流になっている。

データベース設計の手順

データベース設計の手順は以下の通り。

①テーブルの抽出 ②テーブルの定義 ③テーブル構造を整理 ④ER図の作成

①テーブルの抽出

まず、アプリケーションにおいてあらかじめ最低限必要なテーブルを洗い出す。これにより、アプリケーションの開発中に新たなテーブルを慌てて作ることをしなくて済む。途中でテーブルを追加するとテーブル間の関係性を見直す必要が出て、また設計から見直さなくてはならなくなるので面倒である。

②テーブルの定義

テーブルを抽出した後は、各テーブルが持つカラムを決める。この段階でカラムを考える理由もテーブル抽出を行った理由と同様。こちらも、アプリケーションの開発中にカラムを追加すると、アプリケーションのコードを書き直したり、ビューのデザインを変えたりする必要があり、効率が悪い。

④テーブル構造を整理

テーブル構造にはアプリケーションのパフォーマンスを著しく下げるやってはならない構造がある。

A. 同じカラム名を1つのテーブルに複数作成

1つのテーブルに同じカラム名を複数作るようなテーブル構造になりそうになったら、必ず違うテーブルに保存するようにする。 以下は、記事に複数の画像を保存したい時の例です。

articlesテーブル

id title content image1 image2 image3
1 title1 ... photo1.png image1.jpg download1.png
2 title2 ... photo2.png image2.jpg download2.png
3 title3 ... photo3.png image3i.jpg download3.png

上記テーブル構造は悪例。これでは、記事に画像を追加する度にimageカラムを追加していかなくてはならない。 この場合、複数のimageに関するデータを1つのレコードが保持している状況。articles:1 に対して image:多 の関係が成り立っているので、テーブル間の1対多で表現できる。よって、以下のようにテーブルを分ける。

articlesテーブル

id title content
1 タイトル1 ...
2 タイトル2 ...
3 タイトル3 ...

imagesテーブル

id name article_id
1 photo1.png 1
2 image1.jpg 1
3 download1.png 1
4 photo2.png 2
5 image2.jpg 2
6 download2.png 2
7 photo3.png 3
8 image2.jpg 3
9 download3.png 3

これにより、imagesテーブルにレコードを追加していけば記事に画像を際限なく保存することができる。

B. 予約語について

データベースを作る際に一度はつまずいてしまうテーマに、「予約語」がある。 予約語とは、MYSQL側で使用されるためテーブル名やカラム名に設定することができないよう決められている単語のこと。 予約語をテーブル名やカラム名に含んでしまうとエラーになるので、予約語の一覧を確かめておく必要がある。

テーブルの関連付け

データベースに存在する複数のテーブルには関連付けがなされることが多い。

・1対多の関係 1対多とはつまり、ひとつのものに対して複数のものが紐付いているような状態。 1対多の関係性を作るには、 親子関係の子に当たるテーブルに、所属するモデル_id という規則でカラムを追加する。

例) usersテーブル(親)

id nickname email
1 sato sato@com
2 takahashi takahashi@com
3 yamada yamada@com

tweetsテーブル(子)

id title image text user_id
1 title1 photo1.png ... 1
2 title2 image1.jpg ... 2
3 title3 download1.png ... 1
4 title4 photo2.png ... 3
5 title5 image2.jpg ... 3

これまでが、Webアプリケーションの重要な構成要素のひとつであるデータベースについて。 以下で、Railsにおいてデータベースから情報を取得し利用する方法を確認する。

モデル

モデルとは、Railsの中でデータベースへのアクセスをはじめとする情報のやりとりに関する処理を担当しているパートで、実体は1つのクラスが定義された◯◯.rbというファイル。

モデルの使い方

基本的に、モデルはapp/models以下の階層に設置されます。

例)

class Sample

  def test
  end

end

Sampleというクラスが書かれているが、モデルを担当するクラスのことをモデルクラスと言う。例の場合は、Sampleというクラスがモデルクラスに当たる。

テーブルとモデルの結びつきは、名前で決定する

各種情報はテーブルに保存れ、Railsからあるテーブルに保存された情報を引き出す際は、そのテーブルに対応するモデルクラスを用意する。 対応関係は、テーブルとモデルクラスの名前によって決定する。

モデルの命名規則

Railsでは、ファイルやテーブルなどの命名を規則に従った形にする必要がある。モデルとテーブルに関する命名規則は以下のようになっており、これに従えばテーブルとモデルが結びつく。

種類 概要 名前例
モデルクラス名 先頭は大文字、単数形 Tweet
モデルクラスのファイル名 先頭は小文字、単数形 tweet.rb
テーブル名 先頭は小文字、複数形 tweets

Tweetクラスがtweetsテーブルを操作でき、モデルクラスが入ったファイル名はtweet.rbとなる。

モデルとテーブルの設計図を生成する

モデルとテーブルは1つのコマンドで同時に作成することができる。

rails g model コマンド

rails g modelコマンドを使用すると、DBにテーブルを作成するためのファイルとそのテーブルに対応するモデルファイルとを自動で作成することができる。実際に使用する際には「rails g model モデルクラス名(全て小文字)」というように作成したいモデルのクラス名を全て小文字にしたものを後ろに付けて実行する。

例) ターミナルにて

  $ rails g model モデルクラス名(全て小文字)
  # モデルを作成

rails d model コマンド モデルを作成する際は「rails g model」コマンドを使用下が、モデル名を間違えて作成してしまった場合、「rails d model」コマンドを使用してモデルファイルを削除することができる。

テーブルの作成

rails g modelコマンドでモデルのファイルを作成した際、同時にそのモデルと結びつくテーブルの設計図も生成されている。これをマイグレーションファイルと呼ぶ。 この段階ではまだ、DBには該当するテーブルは存在しなく、このマイグレーションファイルを実行することで、初めてテーブルが作られる。

マイグレーションファイル

マイグレーションファイルは、テーブルの設計図のようなもの。マイグレーションファイルにどんなカラムを持つテーブルにするかを記述し、実行することでテーブルが作成される。

※このマイグレーションファイルrails g modelコマンドを実行した際に作成されている。コマンド実行時にターミナルに下記のような表記がされるはずである。

create    db/migrate/20191005090000_create_tweets.rb

マイグレーションファイルの編集

マイグレーションファイルでは、changeというメソッドで、作成するカラムを指定することができる。

class Createテーブル名 < ActiveRecord::Migration[5.2]
  def change
    create_table :テーブル名 do |t|

      t.timestamps
    end
  end
end
カラムの「型」

カラム名を指定するとともに、そこにどんなデータが入るのかを示す「型」も指定する必要がある。 主な型の種類は以下の通り。

説明 用例
integer 数字 ユーザーidなど
string 文字(少) ユーザー名、パスワードなど
text 文字(多) 投稿文など
boolean 真か偽か 真偽フラグ
datetime 日付と時刻 作成日時、更新日時など

例)

  class CreateTweets < ActiveRecord::Migration[5.2]
    def change
      create_table :tweets do |t|
        t.string      :name
        t.text        :text
        t.text        :image
        t.timestamps null: true
      end
    end
  end

作成するカラムとその型を、changeというメソッドの中で指定する。 t.に続くのが「型」で、:に続くのがカラム名

マイグレーションファイルの実行

実際に作った設計図に従ってテーブルを操作するにはrake db:migrateコマンドを使用する。

rake db:migrate

現在存在している未実行のマイグレーションファイルを実行するコマンド。

スキーマファイル

スキーマファイルは、rake db:migrateを実行した際に更新が行われ、最新のマイグレーションファイルのバージョンが記録される。

schema_migrations

schema_migrationsとはデータベースの変更履歴のようなもので、どのマイグレーションファイルまでが実行されているかが記録されていくテーブル。マイグレーションファイルが実行された際に自動的に作成される。

※schema_migrationsを確認するために、Sequel Proを利用する。また、マイグレーションファイルは消してはいけない。実行し終わったマイグレーションファイルを削除してしまうと、schema_migrationsと齟齬が生じ問題が生じる恐れがあるためである。

※Sequel Pro Sequel Pro(シークエルプロ)は、テーブルを見やすく表示してくれるアプリケーション。データベースに接続し、見たいテーブルを選択するとエクセルのような形式で表示される。

一度変更したデータベースの状態を元に戻す

rake db:migrateコマンドを実行した際、読み込んだmigrationファイルの記述が間違っていて、意図しない名前や型を持つテーブルができてしまうことがあるが、そのような場合は、rake db:rollback コマンドを利用する。

※ 誤った名前のカラムを追加してしまった時は、実行したマイグレーションファイルを書き換えて再度 rake db:migrateすれば良いと思うかもしれないが、その方法では正しいカラムを作り直すことはできない。 なぜなら、railsではマイグレーションを一度実行してしまうと、そのマイグレーションを編集して再度マイグレーションを実行することができないためである。

rake db:rollback

これから先新たなテーブルを作成したり、カラムの追加などテーブルに変更を加える際は常にmigrationファイルを作成する。rake db:rollbackコマンドを実行すると、データベースの状態が最新のmigrationファイルを実行する前に戻ることになる。rake db:rollbackコマンドを実行すると最後に行われたマイグレーションファイルの実行が無かったことになるが、migrationファイル自体は残る。そのため、もう一度rake db:migrateコマンドを実行すれば再びテーブルが作成される。

テーブルの情報をRails側で利用する方法

あるテーブルに保存されているレコードの情報を取得するには、名前によって関連付ける必要がある。 例えば、tweetsテーブルに保存されているレコードの情報を利用するためにはTweetクラスを用意する必要があるが、rails g model コマンドでこれらを一度に生成できる。

テーブルと、関連するモデルクラスがある状態であれば、コンソール上から実際にコードを書いてテーブルの情報を取得する方法を試すことができる。 例)tweet = Tweet.find(1)

ActiveRecordの仕組み

なぜ定義されていないfindというメソッドが使えるのかということについてだが、実はrails g modelコマンドで生成されるモデルクラスは全てApplicationRecordというクラスを継承している。

ActiveRecord

ActiveRecord(アクティブレコード)はRubyのGemの一種。このGemはモデルとテーブルをつなぎ合わせることで、Railsからテーブルのレコードにアクセスできるようにする。ActiveRecordRailsにデフォルトでインストールされています。実際に、この機能を利用する際にはApplicationRecordというクラスを継承して使用する。ApplicationRecordというクラスにはテーブルにアクセスして情報を取得するためのメソッドが定義されており、モデルクラスはそれを継承し利用することでテーブルから情報を取得している。

クラスの継承

あるクラスに定義されたメソッドを、別のクラスで利用できるようにすることを継承と言う。

元となるクラスを親クラス、親クラスのメソッドを引き継ぎ新しく作成するクラスを子クラスとする。 クラスを継承した場合、親クラスで定義されているメソッドを子クラスで利用することができる。このとき継承元であるApplicationRecordで定義されているメソッドのことを「ApplicationRecordメソッド」と言う。ApplicationRecordはデータベースにアクセスするためのクラスなので、そのメソッドもデータベースへのアクセスを行うものになる。 以下にメソッド例をいくつか記す。

allメソッド

allメソッドはApplicationRecordを継承したモデルと結びつくテーブルのレコードを全て取得する。

newメソッド/saveメソッド

newメソッドはクラスのインスタンスを生成するメソッド。ApplicationRecordを継承しているモデルクラスの場合、newメソッドを実行すると関連するテーブルのカラム名がキーになったハッシュのようなものが生成される。これをモデルクラスのインスタンスと呼び、インスタンスのそれぞれのキーに値を代入してsaveメソッドを実行するとテーブルに保存される。

createメソッド

createメソッドはレコードの作成を行うことのできるメソッド。newメソッドとsaveメソッドを使用して行った処理をcreateメソッドで一気に行うことができるため、単にレコードを作成する場合にはこのメソッドを使用する。

SQL(エス・キュー・エル)

SQLとは、データベースに対して保存されているデータを要求する時に使用する言語形式。

本来ならデータベースに対しては以下のようなSQL文を使ってデータを要求しなければならないが、railsではActiveRecordのおかげで簡単にデータを要求することができる。

例1) SQL SELECT tweets.* FROM tweets

例2) [1] pry(main)> Tweet.all

上記の2つの例は同じ処理を行っている。Railsでは例2のように記述するだけで、データベースにアクセスする際には自動的に例1のようなSQL文に変換される。このように、ActiveRecordを使用するRailsではより直感的で短い記法でテーブルの情報を操作することができる。

レコードの更新

テーブルに保存されているレコードを更新するにはそのレコードをインスタンスとして取得し、カラムを指定して値を直接代入する。上書きするだけではレコードの値は更新されないので、上書きを保存するにはインスタンスのsaveメソッドを使う。

AWSについて

AWSについて

AWSとはAmazon Web Servisesの略で、Amazonが提供しているクラウドコンピューティングサービスです。   代表的なものとしてAmazon EC2(仮想サーバ)、Amazon S3(クラウドストレージ)、Amazon RDS(データベース)などがあります。

AWSを使うメリット

クラウドコンピューティングを利用するメリットは別記事に載せましたが、それ以外にも自社でサーバを購入し管理するオンプレミスという方法もあります(元々はこちらがメインだったが、サブスクリプション型ビジネスの浸透に伴いクラウド型が浸透してきている印象)。

ひとまずここでシステムやサービスの導入・運用を検討する上でのクラウドコンピューティングとそれ以外を比較してみます。

オンプレミス クラウド レンタル
初期費用 サーバ購入費用がかかる 特にかからない 多少発生する場合がある
ランニングコスト 電気代やサーバ設置料金 期間に伴う使用料 主に月額費用
開始までの期間 数週間〜数ヶ月 数分 数分〜数日
カスタマイズ 可能 不可 不可
セキュリティ ネットワークを隔離して構築すると強固 サービスの用意した仕組みに準拠する ネットワークやデータ領域が他と共用になることがある
スペックの変更 自分で購入して拡張できる場合がある 可能 可能(サーバ移行が必要な場合あり)
システム連携 回線を用意することで設定可能 オプションで対応可能な場合あり 不可
障害対応 自分で復旧か業者委託 サービスの用意した仕組みに準拠する サービスの用意した仕組みに準拠する

一般的に、「必要なリソースが変動しない場合 = 拡大期を終え運用のみとなったサービス」や「特殊なデバイスを利用したい場合」、「セキュリティのためにクラウドコンピューティングを利用できない場合」はオンプレミスを、それ以外の場合には、クラウドコンピューティングを利用することが多くなってきています。

外部キーをもつデータのdestroy機能の搭載について

Railsのアプリケーションの作成時に、外部キーを用いている場合の削除昨日の搭載について記載します。

 

今回作成したモデル

・item.rb

・item_image.rb

 

各々のモデルの関連づけは以下コードの通り。

class Item < ApplicationRecord
~
 
 has_many :item_images
 
~
end
class ItemImage < ApplicationRecord
 belongs_to :item
 
~
end

外部キーによってitemとitem_imageが関連づけられている状態です。

この状態でitemの削除機能を実装すると、下記内容のエラー文が表示されます。

ActiveRecord::StatementInvalid: Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails

 

要因:外部キーの参照整合性

外部キーは、主キーのテーブルと外部キーのテーブルを関連づけ、主キー側のテーブルデータへの変更に制限を加えることができるもの。

今回の例で言うと、itemが削除された際にitemの主キーを参照するitem_imageテーブルのデータは参照先を持たない状態になる可能性があり、外部キーの設定をすることでこれを未然に防いでいます。

 

対策:外部制約キーの設定

上記を解決するには、destoryで外部キーとして設定しているレコード削除させるように、dependent: :destroyを設定すればOK。

class Item < ApplicationRecord
~
 
 has_many :item_images, dependent: :destroy
 
~
end

上記コード記載により、外部キーとして関連づけられているものもまとめて削除することが可能になる。