ウェブアプリ開発日誌:Rails5でユーザ登録機能を実装するまでのあれこれ
Railsアプリにユーザ登録・認証を実装するまでの手順を書きます。ここでいうユーザ登録は、よくあるユーザ名とメールアドレスを入力して届いた認証リンクにアクセスするとアクティベートされるタイプのやつです。
環境
rails new
プロジェクトディレクトリを作ります。今回はデータベースにMySQLを使うのと、テストにはRSpecを使いたいのでデフォルトのテストは外しておきました。
rails new myapp -T -B -d mysql
付与したオプションは次の通り。
-T
デフォルトのテストコード生成を外す-B
生成直後のbundleをスキップする-d mysql
データベースをMySQLに変更する(デフォルトはPostgreSQL)
deviseを導入する
deviseはrailsでユーザ登録・認証機能を実装するgemです。正直ユーザ機能つけるならこれ一択みたいなところがあって、他にもないわけではないですがOAuthやらAPI作成やらとの連携も最も進んでいてわざわざ他のを選ぶ理由がないという感じです。
Gemfileに認証系のgemを追加。
gem 'devise' gem 'omniauth' gem 'omniauth-twitter'
bundle install
後、bundle exec rails g devise:install
でdeviseの設定ファイルを生成します。具体的にはconfig/initializers/devise.rb
とconfig/locales/devise.en.yml
ができますが、それは今のところどうでもよくてその後に表示される手順が重要です。
Some setup you must do manually if you haven't yet: 1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb: config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } In production, :host should be set to the actual host of your application. 2. Ensure you have defined root_url to *something* in your config/routes.rb. For example: root to: "home#index" 3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example: <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> 4. You can copy Devise views (for customization) to your app by running: rails g devise:views
これを一つ一つ順番に実行していきましょう。
Userモデルの作成
認証に用いるモデルを作成します。名前はなんでも構いませんが、特に理由がなければUser
が無難だと思います。
$ rails g devise User
できたモデルファイルを変更します。
class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable, :timeoutable, :omniauthable, omniauth_providers: [:twitter] end
また、マイグレーションファイルからコメントアウトをいくつか外してカラムを追加します。
class DeviseCreateUsers < ActiveRecord::Migration[5.0] def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at ## Trackable t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip ## Confirmable t.string :confirmation_token t.datetime :confirmed_at t.datetime :confirmation_sent_at t.string :unconfirmed_email # Only if using reconfirmable ## Lockable # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts # t.string :unlock_token # Only if unlock strategy is :email or :both # t.datetime :locked_at t.timestamps null: false end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true add_index :users, :confirmation_token, unique: true # add_index :users, :unlock_token, unique: true end end
今回はlockableを省きましたが、アカウントのロック機能を利用する場合はこれもコメントアウトを外します。
figaroを導入
omniauthでtwitter認証を導入するのはいいですがAPIのトークンやシークレットをそのままコードに書くのはダメです。あとで消すつもりでも、うっかりコミットしてしまったりしたら最悪です。 運用では環境変数にこの手のものを指定するのが定石ですが、開発環境でいちいち変数をexportするのも面倒なのでfigaroというgemを使います。 これは変数をファイルに書き出しておいて、それを環境変数として読み込んでくれるやつです。したがって、開発環境ではgitignoreでバージョン管理から外れたファイルで、本番では環境変数で指定といった使い分けができます。
gem 'figaro'
bundle install
したらfigaroで必要になるファイルをインストールしましょう。
bundle exec figaro install
これでconfig/application.yml
というファイルが追加され、.gitignore
にそれが追記されたはずです。このapplication.ymlが環境変数を指定するためのファイルになります。
twitter認証を追加する
Twitter Application Managementにアクセスしてアプリケーションの登録を行います。
APIトークンとシークレットを設定する
入手したトークンとシークレットをapplication.ymlに記述し、それを環境変数として読み込めるようにします。
development: TWITTER_API_KEY: '<token>' TWITTER_API_SECRET: '<secret>'
config/initializers/devise.rb
にomniauthの設定を記述します。figaroが設定した環境変数を読み出すための初期化コードです。
Devise.setup do |config| config.omniauth :twitter, ENV['TWITTER_APP_ID'], ENV['TWITTER_APP_SECRET'] end
これでapplication.ymlを別に共有しておけば開発環境でいちいち変数を設定する手間が省けますし、環境別にファイルを分けたりすることもできます。このファイルの共有方法に関しては議論の余地がありますが、少なくともバージョン管理に含めることが間違いなのは明らかなのでそこからの漏洩リスクはこれでどうにかなりました。
OAuth用のマイグレーションを作成する
ここまでやったらあとはbundle exec rake db:migrate
を実行します。これでデータベースに認証に必要なテーブルができました。
callback用のコントローラを作る
twitterアプリ作成時に指定したコールバックURLを処理できるようにコントローラを作ります。
$ rails g controller omniauth_callbacks
omniauth_callbacks_controller.rb
を以下のように変更する。
class OmniauthCallbacksController < Devise::OmniauthCallbacksController def twitter @user = User.from_omniauth(request.env["omniauth.auth"].except("extra")) if @user.persisted? sign_in_and_redirect @user else session["devise.user_attributes"] = @user.attributes redirect_to new_user_registration_url end end end
参考文献
No.227 簡単ポーカー 解法
No.227 簡単ポーカー - yukicoderを解いた。
手札の役を作る処理は決定性有限オートマトンに置き換えられる。
開始状態はNO HAND
とし、空のスタックを用意してソートした入力リストを先頭から順に積んでいく。ソートは同じ値をグループ化するために行なっているので昇順でも降順でも構わない。
- 入力リストから取り出した値がスタックtopと同じならそのまま積む
- 入力リストから取り出した値がスタックtopと異なるならスタックサイズを入力として状態遷移し、スタックを空にしてから値を積む
状態遷移表は次のようになる。TWO PAIR
が必要ないのは入力リストから取り出した値がスタックtopと同じであるかぎり積んでいくのでTWO PAIR => FULL HOUSE
という遷移はあり得ないため。
# \ s | NO HAND | ONE PAIR | THREE CARD |
---|---|---|---|
2 | ONE PAIR | TWO PAIR | FULL HOUSE |
3 | THREE CARD | FULL HOUSE | - |
(#:スタックサイズ, s:現在の状態)
これを元にパスを作っていくと次のような状態遷移図が出来上がる。
これをPerlで実装した。
sub trans { my ($state, $stack_size) = @_; if ($state eq "NO HAND" && $stack_size == 2) { return "ONE PAIR"; } elsif ($state eq "NO HAND" && $stack_size == 3) { return "THREE CARD"; } elsif ($state eq "ONE PAIR" && $stack_size == 2) { return "TWO PAIR"; } elsif ($state eq "ONE PAIR" && $stack_size == 3) { return "FULL HOUSE"; } elsif ($state eq "THREE CARD" && $stack_size == 2) { return "FULL HOUSE"; } else { return $state; } } my @A = sort { $a <=> $b } map { $_ + 0 } split ' ', <>; my $state = "NO HAND"; my @stack = (); foreach (@A) { if ($#stack >= 0 && $_ != $stack[$#stack]) { $state = trans($state, $#stack+1); @stack = (); } push @stack, $_; } print(trans($state, $#stack+1) . "\n");
関連記事
No.231 めぐるはめぐる (1) 解法
No.231 めぐるはめぐる (1) - yukicoderを解いた。
面倒なので解説にあるとおり、一番簡単な解法を選ぶ。
一番効率のいい狩場に6時間行って,可能ならそれ,不可能なら不可能が一番楽です.
6時間で経験値3000000以上を得られるダンジョンが存在するかどうか、存在する場合は何行目のダンジョンかという情報が必要になる。
よって下記のように答えが求まる。
my $N = <> + 0; for (1..$N) { my ($G, $D) = map { $_ + 0 } split ' ', <>; if (($G - 30000 * $D) * 6 >= 3000000) { print("YES\n" . "$_\n" x 6); exit(0); } } print "NO\n";
JavaScriptテスト環境セットアップ: Node.js編
Node.jsでテスト書くためのスタックとか設定を書く。
あくまで僕がいつも使ってるやつなので他にもっといいのあったら教えてください。
スタック
Node.jsのテストはだいたい次の4つのパッケージで完結してる。フロントのテストはもっとめんどくさい。
name | description |
---|---|
mocha | Node.jsのテストランナー |
chai | BDD/TDDスタイルでテストを書くためのライブラリ |
sinon | JavaScriptのモックライブラリ |
sinon-chai | chaiでsinonを使うためのchaiプラグイン |
$ npm install mocha chai sinon sinon-chai -D
セットアップ
globalにテスト中に使う変数を入れておく
僕はいつもExpect形式で書くのだけど、そのままだとテストファイルの冒頭に以下のような設定を毎回書かないといけない。
const chai = require('chai'); const sinon = require('sinon'); const sinonChai = require('sinon-chai'); chai.use(sinonChai);
それは面倒だし、メンテナンス性も悪いので別ファイルにまとめておいて、mocha起動時にそれを読み込ませる。例えば/test/globals.js
というファイルを作ってglobalにexpectを突っ込んでおく。
const chai = require('chai'); const sinon = require('sinon'); const sinonChai = require('sinon-chai'); chai.use(sinonChai); global.expect = chai.expect;
npm testを定義する
作成したglobal.js
をテスト前に読み込むにはpackage.json
のscripts:test
項目に次のようにコマンドを割り当てておく。
"scripts": { "test:main": "$(npm bin)/mocha './test/**/*.spec.js' -r ./test/globals --recursive" }
すると'./test/globals.js'がrequire
された状態でテストが開始するので書くファイルにexpectの宣言を書かずに使用できる。
こんな感じで。
const API = require('./api'); describe('create API client', () => { const client = new API(); it('fetch user info successfully', done => { const user = client.getUserInfo(); expect(user) .to.have.property('name') .that.is.a('string'); done(); }); });
Travis CI
追加でTravis CIの設定をして自動化する。
とはいってもここまでやるともう雑な.travis.yml
を用意するだけで良い。
language: node_js node_js: - 6 - 5 - 4
次はフロント側のテストについて書く。
関連記事
homebrew-caskの書き方とpull-requestの出し方
Mac使いならほとんどの人がお世話になっているHomebrew。 その拡張であるHomebrew Caskは以前なら別途インストールする必要があったが、色々ゴタゴタがあった末に本体に統合された模様。
僕は環境構築をできる限り自動化していてMacのプロビジョニングを行うにあたってはこれがないと結構面倒だ。もしHomebrew Caskのリポジトリに必要なCaskがなければ自分で作ってプルリクを出したら他にも幸せになる人がいるかもしれない。今回Windowsで使っていた3DモデリングソフトがいつのまにかMacに対応していたのでCaskに固めてみた次第。
Caskを作成する
まず最新のHomebrewが導入された状態で次のコマンドを実行する。hogehoge
は作成するCaskの名前に読み替えてほしい。
$ brew cask create hogehoge
するとhogehoge.rb
ができるのでこれに必要な情報を書き加えていく。
cask 'hogehoge' do version '' sha256 '' url '' name '' homepage '' app '' end
項目 | 内容 |
---|---|
version | アプリケーションのバージョンを指定する |
sha256 | 改ざんや破損検知のためSHA256でイメージやアーカイブのチェックサムを取る場合、ここにハッシュを入れておく。スキップする場合は:no_check シンボルを指定する。 |
url | ダウンロードするファイルのURL。CDNなどを利用していてhomepage で指定するドメインと異なる場合は# <ダウンロード先のドメイン> was verified as official when first introduced to the cask というコメントを一行上につけないとLintで弾かれる。 |
name | アプリケーションの名前を指定する。 |
homepage | アプリケーションのホームページを指定する。多くの場合はコーポレートサイトの製品ページなど。 |
app | .appファイルの名前を書いておくと.dmgがマウントされた後にこれが/Application にコピーされる。 |
Macのアプリケーションは様々なインストール手段があるので、app
では対応できないことがある。例えば.dmgの中身が.pkg形式のインストーラだった場合。appではなくpkgを使用する。
また、この場合uninstallの項目にベンダーのIDを指定する必要があるが、これはhomebrew-caskのリポジトリにあるスクリプトで確認できる。
$ ./developer/bin/list_recent_pkg_ids com.tetraface.Metasequoia.pkg com.microsoft.OneDrive-mac com.autodesk.mas.123ddesign com.agilebits.onepassword-osx com.microsoft.office.all.core.pkg.14.6.8.update com.microsoft.office.all.dcc.pkg.14.6.8.update com.microsoft.office.all.excel.pkg.14.6.8.update com.microsoft.office.all.fix_permissions.pkg.14.6.8 com.microsoft.office.all.outlook.pkg.14.6.8.update com.microsoft.office.all.powerpoint.pkg.14.6.8.update
最終的には以下のような状態になる。
cask 'hogehoge' do version '1.0.0' sha256 '0cd88d99379bdf8dfa42fd13ee970d8921f6d5eb3d892f76e4a7c76d68dc9e6d' # cdn.hogehoge.net was verified as official when first introduced to the cask url "https://cdn.hogehoge.net/download/Hogehoge-#{version}-Installer.dmg" name 'Hogehoge' homepage 'https://www.hogehoge.com/' pkg "Hogehoge-#{version}-Installer.pkg" uninstall pkgutil: 'com.pkg.hogehoge' end
プルリクを出す
結構ガイドラインが細かったりするので目を通しておいた方がいいが、プルリク時のテンプレートにチェックボックスが用意されていて基本的にはこれを満たしていれば問題ない。
brew cask audit --download {{cask_file}}
でエラーが出ないかどうかbrew cask style --fix {{cask_file}}
でエラーが出ないかどうか- コミットメッセージにCaskの名前とバージョンを記載しているか
- token referenceに沿って名前を付けているか
brew cask install {{cask_file}}
でエラーが出ないかどうかbrew cask uninstall {{cask_file}}
が正常に動くかどうか- すでに同じアプリケーションのプルリクが出ていたり過去に却下されていないか
本体のリポジトリにマージされるとbrew cask install hogehoge
でインストールできるようになるので、ansibleのplaybookには
homebrew_cask_packages: - { name: hogehoge }
とか書いておくと新型に買い換えたときの環境構築で幸せになれる。