12. 既存データの読み込みと認証の続き

現在のシステムでも、開発環境のデータは運用環境のデータを流用している。これは、運用会社がサポートをするにあたって、ユーザと同じ環境にないと確認ができないためである。ただし、学内の機密データに関するものは、個人情報をマスキングしたり、データ自体を削除したりしている。今回もこのマスキングされたデータを、開発に使っていく予定である。

システムは PostgreSQL で作成してしまっているので、PostgreSQL 固有のテーブルダンプ・リストアコマンドである \copy コマンドを使っている。今回もテーブルごとに吐き出されたデータを db:seed で読み込むことにする。ただし、間違えて本番環境のデータを破壊しないように、テーブルに一つでもデータが入っている場合には読み込みをしない。

\copy で吐き出したダンプデータは dump に保持することとする。これはリポジトリに入れないので、.gitignore に無視するファイルとして設定する。同時にマスクしたデータをここに入れておく。

# dump files
/dump

ダンプデータは psql 上の \copy コマンドで実行する。以下のようなスクリプトを bin/store_database.sh として用意する。テーブルのデータを全削除してから、\COPY コマンドで流し込みを行う。

#!/bin/sh
HOST=$1
USER=$2
DB=$3
TABLE=$4
COLUMNS=$5
FNAME=$6
PASSWORD=$7
echo "### load ${TABLE}"
psql -U $USER -h $HOST $DB << EOF
DELETE from $TABLE;
\COPY $TABLE ($COLUMNS, created_at, updated_at) FROM '$FNAME' 
EOF

rake タスクの db:seed からこのスクリプトを叩くようにする。db/seeds.rb を以下のように記述した。旧システムではデータベース名などは決め打ちしていたが、今回は、正しく DRY にするために、Rails.application.config.database_configuration からちゃんと取得している。db:seed ではデータがまったく登録されていない場合のみ、store_database.sh を呼ぶようにしている。\COPY コマンド終了後には、各テーブルの主キーの sequence をリセットしている。

require 'active_support'

class CopyFromDump < ActiveRecord::Migration

  def self.store_database_from_dump(table, dir = "dump")
    storeClass = table.classify.constantize
    fname = File.join(dir, "#{table}.sql")
    env = Rails.env
    database_configuration = Rails.application.config.database_configuration[env]
    host = database_configuration["host"]
    username = database_configuration["username"]
    database = database_configuration["database"]
    password = database_configuration["password"]
    if storeClass.first == nil
      system("bin/store_database.sh #{host} #{username} #{database} #{table} \"#{storeClass::LoadKey}\" #{fname} #{password}")
      print "#{ActiveRecord::Base.connection.reset_pk_sequence!(table)}\n"
    end
  end

  def self.up
    Rails.application.config.CopyTables.each { |key| self.store_database_from_dump key }
  end
end

CopyFromDump.up

コピーすべきテーブル名は Rails.application.config.CopyTables から取得する。これは、config/application.rb で以下のように設定する。テーブルが増えれば %w の中身が増殖していく。

    config.CopyTables = %w( years users )

\COPY コマンドの列名は各モデルクラスの LoadKey という変数から取得する。app/models/year.rb の該当部はこうなる。

class Year < ActiveRecord::Base
  LoadKey = 'id, year, default_year'

同様に、app/model/user.rb の該当部はこうなる。

class User < ActiveRecord::Base
  LoadKey = 'id, encrypted_password, password_salt, reset_password_token, remember_token, remember_created_at, sign_in_count, current_sign_in_at, last_sign_in_at, current_sign_in_ip, last_sign_in_ip, kyouin_id, gakusei_id, shokuin_id, name, email'

これだけの設定をすると、データベースに流し込みができるはずである。実行した結果がこちら。各テーブルごとに削除した数、コピーした数、次に設定される主キーが表示される。users の方で COPY の数値がかなり少ないのは削除されたデータも存在するからである。

$ bin/rake db:seed
### load years
DELETE 0
COPY 17
18
### load users
DELETE 0
COPY 3737
4134

認証のテスト

まず、welcome#index の描画に認証が必要となるように設定してみる。app/controller/welcome_controller.rb に以下の行を追加する。

  before_action :authenticate_user!

この状態で、ブラウザでアクセスするとこんな画面になる。

気になるのは以下の部分。

  • 警告文が英語であること
  • キーは name に変更したのに Email を入力するようになっていること
  • 画面の左端にくっついていること

まず、最初の言語の部分を修正する。config/application.rb に i18n の default_locale を設定する部分があるので、そこを :ja に変更する。また、サブフォルダの locale も読み出すようにパスを修正する。

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
    config.i18n.default_locale = :ja

locale ファイルは前日の設定で配置済であるため、警告メッセージが日本語に変わっていることを確認できる。メッセージの内容が若干システムと合わないので、locale ファイルを後で修正しておく。

また、GitHub から日本語辞書ファイルをダウンロードしておく(参考: Railsの多言語化対応 I18nのやり方を整理してみた!【国際化/英語化】)。

$ mkdir config/locales/{defaults,models,views}
$ wget https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml -P config/locales/defaults

次に user モデルの locale ファイルを作成する。先ほど作った models フォルダの下にさらに users フォルダを作成し、そこに ja.yml ファイルを作成する。config/locales/models/users/ja.yml は以下のようになる。

ja:
  activerecord:
    attributes:
      user:
        name: "アカウント名"
        password: "パスワード"
        remember_me: "次回からパスワード入力を省く"

二番目以降を修正するために、カスタム view を作成する(前回ペンディングにした部分である)。今回は、users と admin の二つのモデルを作成する予定だが、ログイン画面は共通でよいので、scoped_view は true にはしない。

$ bin/rails g devise:views
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

devise 1.2 以前と違って、haml ジェネレータは削除されてしまったので、erb を haml に変換する。

$ for file in app/views/devise/**/*.erb; do bundle exec html2haml -e $file ${file%erb}haml && rm $file; done
/Users/hkob/rails/webcit3/vendor/bundle/ruby/2.1.0/gems/html2haml-1.0.1/lib/html2haml/html.rb:346: warning: wrong element type nil at 0 (expected array)
/Users/hkob/rails/webcit3/vendor/bundle/ruby/2.1.0/gems/html2haml-1.0.1/lib/html2haml/html.rb:346: warning: ignoring wrong elements is deprecated, remove them explicitly
/Users/hkob/rails/webcit3/vendor/bundle/ruby/2.1.0/gems/html2haml-1.0.1/lib/html2haml/html.rb:346: warning: this causes ArgumentError in the next release
(以下、これの繰り返し)

今回必要なものはログイン処理のみなので、app/views/devise/sessions/new.html.haml を修正する。locale を設定したので、f.label などはシンボル名だけで自動的に日本語が表示される。

.container-fluid
  %h2 ログイン
  = form_for(resource, as:resource_name, url:session_path(resource_name), html:{class:'form-horizontal'}) do |f|
    %fieldset
      .form-group
        = f.label :name, class:'control-label'
        = f.text_field :name, autofocus: true, class:'form-control'
      .form-group
        = f.label :password, class:'control-label'
        = f.password_field :password, autocomplete: "off", class:'form-control'
      - if devise_mapping.rememberable?
        .form-group
          .checkbox-inline
            = f.check_box :remember_me
            = f.label :remember_me
      .actions
        = f.submit "ログイン", class:"btn btn-primary"
= render "devise/shared/links"

描画される画面はこんな感じ。

試しにログインに失敗してみる。エラーメッセージがメールアドレスになっているので、文章は後で修正する。

ログインに成功すると、正しく welcome#index が描画される。

一方、Guard の方は以下のようなエラーが出てしまっている。

WelcomeController
  GET index
    returns http success (FAILED - 1)

welcome#index を描画するには認証が必要となったためである。

早速、Devise の本家に書いてあるように spec/rspec_helper.rb に以下を記述する。

  config.include Devise::TestHelpers, type: :controller

また、特定の FactoryGirl を使ってログインするメソッド login_user_as を同じく spec/rspec_helper.rb に追記しておく(このメソッドは後日修正が入る)。

def login_user_as(fg_key)
  sign_in :user, FactoryGirl.create(fg_key)
end  

次に、spec/factories/users.rb の FactoryGirl の設定をする。今後、いくつかの権限を持ったユーザを増やすので、継承ファクトリを作成する。

FactoryGirl.define do
  factory :user do
    factory :user_hkob do
      name: 'hkob'
    end
  end
end

welcome#index を描画する前に login_user_as を実行することで、エラーを回避する。

require 'rails_helper'

RSpec.describe WelcomeController, :type => :controller do
  describe "GET index" do
    it "returns http success" do
      login_user_as :user_hkob
      get :index
      expect(response).to have_http_status(:success)
    end
  end
end

この結果、WelcomeController の GET index のテストも無事通過するようになった。User のテストがなくてペンディングになっているのを除き、テストが無事に動作した。

WelcomeController
  GET index
    returns http success

今日はここまで。

written by iHatenaSync