Go 言語で gorilla/mux, gorilla/context, gorilla/sessions を使ったユーザーログイン機能のサンプル
はじめに
Go 言語練習のため gorilla/mux, gorilla/context, gorilla/sessions を使ったユーザーログイン機能を実装してみました。
ユーザーの管理はメモリ上の map 型変数にユーザー名とパスワードを入れているだけなので、ここをDBに変えるのが次の課題のつもりです。
package main import ( "net/http" "html/template" "github.com/gorilla/mux" "github.com/gorilla/context" "github.com/gorilla/securecookie" "github.com/gorilla/sessions" "errors" ) var ( // セッションストアの初期化 store *sessions.CookieStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(64)) // 登録ユーザーをメモリ上で管理 users map[string]string = make(map[string]string) ) const ( SessionName = "session-name" ContextSessionKey = "session" ) func main() { r := mux.NewRouter() handleFunc(r, "/", rootHandler) handleFunc(r, "/register", registerGetHandler).Methods("GET") handleFunc(r, "/register", registerPostHandler).Methods("POST") handleFunc(r, "/login", loginGetHandler).Methods("GET") handleFunc(r, "/login", loginPostHandler).Methods("POST") handleFunc(r, "/logout", logoutGetHandler).Methods("GET") handleFunc(r, "/logout", logoutPostHandler).Methods("POST") http.ListenAndServe(":8080", r) } // アプリケーション共通処理を常に呼び出すための糖衣構文 func handleFunc(r *mux.Router, path string, fn http.HandlerFunc) *mux.Route { return r.HandleFunc(path, applicationHandler(fn)) } // アプリケーション共通処理 func applicationHandler(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // セッションの取得 session, err := store.Get(r, SessionName) if err != nil { // 不正なセッションだった場合は作り直す session, err = store.New(r, SessionName) checkError(err) } context.Set(r, ContextSessionKey, session) // 個別のハンドラー呼び出し fn(w, r) } } // トップページ func rootHandler(w http.ResponseWriter, r *http.Request) { type page struct { Username string } p := &page{} session, err := getSession(r) checkError(err) if v, ok := session.Values["username"]; ok { p.Username = v.(string) } executeTemplate(w, "template/index.html", p) } // 登録ページ func registerGetHandler(w http.ResponseWriter, r *http.Request) { executeTemplate(w, "template/register.html", nil) } // 登録処理 func registerPostHandler(w http.ResponseWriter, r *http.Request) { // フォームのパース r.ParseForm() // フォームの値の取得 username, password := r.Form["username"][0], r.Form["password"][0] // 空なら登録ページへ if username == "" || password == "" { http.Redirect(w, r, "/register", http.StatusFound) return } // ユーザー登録 users[username] = password // 現在のユーザーをセッションで管理 session, err := getSession(r) checkError(err) session.Values["username"] = username session.Save(r, w) // トップページへ http.Redirect(w, r, "/", http.StatusSeeOther) } // ログインページ func loginGetHandler(w http.ResponseWriter, r *http.Request) { executeTemplate(w, "template/login.html", nil) } // ログイン処理 func loginPostHandler(w http.ResponseWriter, r *http.Request) { // フォームのパース r.ParseForm() // フォームの値の取得 username, password := r.Form["username"][0], r.Form["password"][0] // 空ならログイン画面へ if username == "" && password == "" { http.Redirect(w, r, "/login", http.StatusFound) return } // ユーザーが登録済みでありパスワードが一致したらログイン状態に if v, ok := users[username]; ok && v == password { session, err := getSession(r) checkError(err) session.Values["username"] = username session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } else { http.Redirect(w, r, "/login", http.StatusSeeOther) } } // ログアウトページ func logoutGetHandler(w http.ResponseWriter, r *http.Request) { executeTemplate(w, "template/logout.html", nil) } // ログアウト処理 func logoutPostHandler(w http.ResponseWriter, r *http.Request) { session, err := getSession(r) checkError(err) delete(session.Values, "username") session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } // セッションの取得 func getSession(r *http.Request) (*sessions.Session, error) { if v := context.Get(r, ContextSessionKey); v != nil { return v.(*sessions.Session), nil } return nil, errors.New("failed to get session") } // テンプレートの実行 func executeTemplate(w http.ResponseWriter, name string, data interface{}) { t, err := template.ParseFiles(name) checkError(err) err = t.Execute(w, data) checkError(err) } // エラーチェック func checkError(err error) { if err != nil { panic(err) } }
おわりに
net/http のハンドラーをチェインさせて共通処理を切り出すパターンは「Go言語によるWebアプリケーション開発」読んだらちゃんと書いてあったのでちゃんと読もうと思いました。
学習目的で gorilla/mux で実装してみてて実際勉強になってますが、普通に web アプリ作るならたぶん goji とか WAF を使った方がいい気がしてきてます。
Go 言語で gorilla/mux を使った簡単なウェブアプリのサンプル
はじめに
Go 言語で簡単なウェブアプリを作りたいので軽量なウェブツールキット gorilla/mux を使ったサンプル。
context の使い方は gin や goji のミドルウェアの実装を参考に、共通化したい処理を個別のハンドラの上にラップする形にした。おきまりのパターンっぽいので Usage とかに書いて欲しい。
package main import ( "fmt" "net/http" "github.com/gorilla/mux" "github.com/gorilla/context" ) func main() { r := mux.NewRouter() // 単純なハンドラ r.HandleFunc("/", YourHandler) // パスに変数を埋め込み r.HandleFunc("/hello/{name}", VarsHandler) // パス変数で正規表現を使用 r.HandleFunc("/hello/{name}/{age:[0-9]+}", RegexHandler) // クエリ文字列の取得 r.HandleFunc("/hi/", QueryStringHandler) // 静的ファイルの提供 // $PROROOT/assets/about.html が http://localhost:8080/assets/about.html でアクセスできる r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets")))) // リダイレクト r.HandleFunc("/moved", RedirectHandler) // マッチするパスがない場合のハンドラ r.NotFoundHandler = http.HandlerFunc(NotFoundHandler) // 複数のハンドラで共通の処理を実行する // 今回はcontextのセットとゲットを試しているが、同じパターンでDBの初期化や認証処理やログ書き出しなどにも応用できる // ハンドラを引き渡すには http.Handler 型を使い func(http.ResponseWriter, *http.Request) から http.Handler への変換には http.HandlerFunc を利用する // http.Handler をハンドラとして登録する場合は Router.Handle を利用する r.Handle("/some1", UseContext(http.HandlerFunc(SomeHandler1))) r.Handle("/some2", UseContext(http.HandlerFunc(SomeHandler2))) // http://localhost:8080 でサービスを行う http.ListenAndServe(":8080", r) } func YourHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Gorilla!\n")) } func VarsHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) fmt.Fprintf(w, "%s Loves Gorilla\n", vars["name"]) } func RegexHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) fmt.Fprintf(w, "%s is %s years old\n", vars["name"], vars["age"]) } func QueryStringHandler(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() fmt.Fprintf(w, "%s Loves Gorilla\n", q.Get("name")) } func RedirectHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusFound) } func NotFoundHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Gorilla!\nNot Found\n")) } const MyContextKey = 1 func UseContext(handler http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { context.Set(r, MyContextKey, "Call SomeMiddleware") handler.ServeHTTP(w, r) } return http.HandlerFunc(fn) } func SomeHandler1(w http.ResponseWriter, r *http.Request) { contextVal := context.Get(r, MyContextKey) fmt.Fprintf(w, "%s Call SomeHandler1", contextVal) } func SomeHandler2(w http.ResponseWriter, r *http.Request) { contextVal := context.Get(r, MyContextKey) fmt.Fprintf(w, "%s Call SomeHandler2", contextVal) }
Mac + DockerToolbox 環境の Centos6 で Supervisor を使って複数のサービスを起動する 〜 sshd と named を起動する例
はじめに
Dockerfile の用意
FROM centos:6 ### sshd のインストール RUN yum -y install initscripts MAKEDEV RUN yum check RUN yum -y update RUN yum -y install openssh-server passwd ### sshd の設定 RUN sed -ri 's/^#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config RUN sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -ri 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config ### sshd の起動準備 RUN /etc/init.d/sshd start RUN /etc/init.d/sshd stop ### パスワードなしで接続 RUN passwd -d root ### named のインストール RUN yum install -y bind RUN /etc/init.d/named start RUN /etc/init.d/named stop ### Supervisor のインストール RUN yum install -y wget RUN wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | python RUN easy_install supervisor COPY supervisord.conf /etc/supervisord.conf CMD /usr/bin/supervisord -c /etc/supervisord.conf
Supervisor の設定ファイルの用意
- Dockerfile と同じディレクトリに supervisord.conf として用意
[inet_http_server] port=127.0.0.1:9001 [supervisord] logfile=/var/log/supervisord.log ; (main log file;default $CWD/supervisord.log) logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) logfile_backups=10 ; (num of main logfile rotation backups;default 10) loglevel=info ; (logging level;default info; others: debug,warn) pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) nodaemon=true ; (start in foreground if true;default false) minfds=1024 ; (min. avail startup file descriptors;default 1024) minprocs=200 ; (min. avail process descriptors;default 200) [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] serverurl=http://127.0.0.1:9001 [program:sshd] command=/usr/sbin/sshd -D autostart=true autorestart=true [program:named] command=/usr/sbin/named -u named -f process_name=%(program_name)s numprocs=1 directory=/var/named priority=100 autostart=true autorestart=true startsecs=5 startretries=3 exitcodes=0,2 stopsignal=TERM stopwaitsecs=10 redirect_stderr=false stdout_logfile=/var/log/named_supervisord.log stdout_logfile_maxbytes=1MB stdout_logfile_backups=10 stdout_capture_maxbytes=1MB
イメージの作成
$ docker build -t centos:supervisor .
コンテナの起動
$ docker run -p 20022:22 -p 20053:53 -d centos:supervisor
接続確認
$ ssh -p 20022 root@127.0.0.1
サービスの管理
- コンテナ上で実行
$ supervisorctl status #=> サービスの確認 $ supervisorctl restart named #=> サービスの再起動
Mac + DockerToolbox で ssh 可能な CentOS のイメージを作成する
DockerToolbox のインストール
http://docs.docker.com/mac/step_one/ の手順に従ってインストール
Docker Quickstart Terminal の実行
Dockerfile の作成
- Docker 用のディレクトリを作成して Dockerfile の作成
$ mkdir -p /path/to/docker/centos-ssh $ cd /path/to/docker/centos-ssh
FROM centos:6 RUN yum -y install initscripts MAKEDEV RUN yum check RUN yum -y update RUN yum -y install openssh-server passwd # 空パスワードの場合は以下をコメントアウト RUN sed -ri 's/^#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config RUN sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -ri 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config RUN /etc/init.d/sshd start RUN /etc/init.d/sshd stop # 空パスワードの場合は以下をコメントアウト RUN passwd -d root # 任意のパスワードの場合は以下をコメントアウト & パスワードを書き換える # RUN echo 'root:root' | chpasswd EXPOSE 22 CMD /usr/sbin/sshd -D
イメージの作成
$ docker build -t centos-ssh .
コンテナの起動
$ docker run -p 20022:22 -d centos-ssh
ポートフォワーディングの設定
- コンテナ起動時に -p オプションで、コンテナから docker-machine の間のポートフォワーディングの設定を行った
- Mac の場合はさらに、docker-machine と Mac の間のポートフォワーディングの設定を行う必要がある
手順
- アプリケーションから VirtualBox の起動
- "default" を選択して "設定" ボタンを押下
- "ネットワーク" を選択して "ポートフォワーディング" を押下
- ポートフォワード設定の追加
ssh 接続実行
$ ssh -p 20022 root@127.0.0.1
Rails で作った API サーバーに Unity でリクエストをする
はじめに
- http://nirasan.hatenablog.com/entry/2015/08/20/172719 で作成した API サーバーに Unity で認証してリクエストを送信するメモ
サーバー側の変更点
- Doorkeeper でアクセストークンの更新に対応するため config/initializers/doorkeeper.rb に以下を追記する。
use_refresh_token
スクリプト
- 任意のオブジェクトに以下のスクリプトをアタッチして使用する
- AuthorizedRequest メソッドに URL とリクエスト成功時のコールバック関数などを指定すると、自動で認証を行った上でリクエストを送信してくれる。
using UnityEngine; using System.Collections; using System.Collections.Generic; using MiniJSON; using System; public class APIManager : MonoBehaviour { public string TokenUrl = "http://127.0.0.1:3000/oauth/token"; public string UsersUrl = "http://127.0.0.1:3000/users/100"; public string client_id = "07461c1f68870ea090ed20af49d0680782950119213e2a1af9e1f653c688dca3"; public string client_secret = "20dea83f022c9e2a0e6fbec1da0e9c0e20c40e1bf09d2e8db029b461653a88bd"; public string username = "user1@example.com"; public string password = "password"; public string AccessToken; public long ExpiresIn; public long CreatedAt; public string RefreshToken; private static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public DateTime ExpiresAt; void Start () { StartCoroutine(GetUsers()); } /// <summary> /// 使用例としてユーザー情報を取得する /// </summary> public IEnumerator GetUsers () { yield return StartCoroutine(AuthorizedRequest( url: UsersUrl, onSuccess: (WWW www) => { Debug.Log(www.text); Debug.Log(www.error); foreach (var kv in www.responseHeaders) { Debug.Log(string.Format("{0} : {1}", kv.Key, kv.Value)); } } )); } /// <summary> /// 認証が必要なページにHTTPリクエストを送信する /// アクセストークンが未取得であれば取得し /// アクセストークンが期限切れであれば更新する /// </summary> /// <param name="url">URL.</param> /// <param name="form">Form.</param> /// <param name="headers">Headers.</param> /// <param name="onSuccess">On success.</param> /// <param name="onError">On error.</param> public IEnumerator AuthorizedRequest (string url, WWWForm form = null, Dictionary<string, string> headers = null, System.Action<WWW> onSuccess = null, System.Action onError = null) { if (string.IsNullOrEmpty(AccessToken)) { var tokenGet = TokenGet(); yield return StartCoroutine(tokenGet); if (Request.RequestIsError(tokenGet)) { yield break; } } if (ExpiresAt < DateTime.Now) { var tokenRefresh = TokenRefresh(); yield return StartCoroutine(tokenRefresh); if (Request.RequestIsError(tokenRefresh)) { yield break; } } if (headers == null) { headers = new Dictionary<string, string>(); headers.Add("Authorization", string.Format("Bearer {0}", AccessToken)); } var request = new Request(url, form, headers, onSuccess, onError); yield return StartCoroutine(request.Send()); yield return request; } /// <summary> /// アクセストークンを取得する /// </summary> IEnumerator TokenGet () { var form = new WWWForm(); form.AddField("grant_type", "password"); form.AddField("username", username); form.AddField("password", password); form.AddField("client_id", client_id); form.AddField("client_secret", client_secret); var request = new Request(url: TokenUrl, form: form, onSuccess: TokenUpdate); yield return StartCoroutine(request.Send()); yield return request; } /// <summary> /// アクセストークンを更新する /// </summary> IEnumerator TokenRefresh () { var form = new WWWForm(); form.AddField("grant_type", "refresh_token"); form.AddField("refresh_token", RefreshToken); form.AddField("client_id", client_id); form.AddField("client_secret", client_secret); var request = new Request(url: TokenUrl, form: form, onSuccess: TokenUpdate); yield return StartCoroutine(request.Send()); yield return request; } /// <summary> /// アクセストークン取得リクエストのレスポンスからアクセストークン情報を取得しインスタンス変数に格納する /// </summary> /// <param name="www">Www.</param> private void TokenUpdate (WWW www) { var jsonDict = Request.WWWToJson(www); AccessToken = (string)jsonDict["access_token"]; ExpiresIn = (long)jsonDict["expires_in"]; CreatedAt = (long)jsonDict["created_at"]; RefreshToken = (string)jsonDict["refresh_token"]; ExpiresAt = UNIX_EPOCH.AddSeconds(CreatedAt + ExpiresIn).ToLocalTime(); } private class Request { public bool IsError { private set; get; } private float TIMEOUT = 5f; private string url; private byte[] formdata; private Dictionary<string, string> headers; private System.Action<WWW> onSuccess; private System.Action onError; public Request (string url, WWWForm form = null, Dictionary<string, string> headers = null, System.Action<WWW> onSuccess = null, System.Action onError = null) { this.url = url; this.formdata = form == null ? null : form.data; this.headers = headers; this.onSuccess = onSuccess == null ? (WWW www) => {} : onSuccess; this.onError = onError == null ? () => {} : onError; } public IEnumerator Send () { /* Debug.Log(url); if (headers != null) { foreach (var kv in headers) { Debug.Log(string.Format("{0} : {1}", kv.Key, kv.Value)); } } */ var www = new WWW(url, formdata, headers); var endtime = Time.realtimeSinceStartup + TIMEOUT; while (!www.isDone || Time.realtimeSinceStartup < endtime) { yield return null; } if (WWWIsError(www)) { this.IsError = true; onError(); } else { this.IsError = false; onSuccess(www); } /* Debug.Log(www.text); Debug.Log(www.error); foreach (var kv in www.responseHeaders) { Debug.Log(string.Format("{0} : {1}", kv.Key, kv.Value)); } */ yield return this; } private bool WWWIsError (WWW www) { return !string.IsNullOrEmpty(www.error); } public static Dictionary<string, object> WWWToJson (WWW www) { return Json.Deserialize(www.text) as Dictionary<string, object>; } public static bool RequestIsError (IEnumerator request) { return ((Request)request.Current).IsError; } } }
Rails で API サーバーの認証の仕組みを作る
はじめに
参考サイト
導入
新しいプロジェクトを作成
$ rails new -T -B doorkeeper-test
gem のインストール
デフォルトの Gemfile に以下を追記
gem 'doorkeeper' gem 'sorcery' group :development, :test do gem 'rspec-rails' gem 'factory_girl_rails' gem 'database_rewinder' end
インストール
$ bundle install
Sorcery のインストール
# Sorcery の設定ファイルと Sorcery に対応した User モデルの生成 $ rails generate sorcery:install
Doorkeeper のインストール
# 設定ファイルの作成とルートの追加 $ rails generate doorkeeper:install # マイグレーションファイルの作成 $ rails generate doorkeeper:migration
マイグレーションの実行
$ rake db:migarate
Doorkeeper の設定
Doorkeeper.configure do # Change the ORM that doorkeeper will use (needs plugins) orm :active_record resource_owner_authenticator do User.find_by_id(session[:current_user_id]) || redirect_to(login_url) end resource_owner_from_credentials do User.authenticate(params[:username], params[:password]) end end Doorkeeper.configuration.token_grant_types << "password"
ユーザーコントローラの作成
- ユーザーの作成とユーザー情報表示ページを作成する
- ユーザー作成は認証なしで、ユーザー情報表示は認証必須のページにする
generate
$ rails g controller users create show
routes.rb
resources :users, only: [:create, :show]
app/controllers/users_controller.rb
class UsersController < ApplicationController before_action :doorkeeper_authorize!, only: [:show] # show のみ認証が必要 def create @user = User.new(user_params) if @user.save render json: @user else head :bad_request end end def show render json: User.find(params[:id]) end private def user_params params.require(:user).permit(:email, :password) end end
app/controllers/application_controller.rb
- CSRF 対策を無効にする
- protect_from_forgery with: :exception + protect_from_forgery with: :null_session
Doorkeeper の認証用キーの作成
- ブラウザを使って Doorkeeper で認証するためのアクセスキーとシークレットキーを取得
- http://127.0.0.1:3000/oauth/applications にアクセスして New Application からアプリケーションを作成後、キーが表示される
- http://qiita.com/camelmasa/items/1c6d06713fc25eb7bddf こちらのページがスクリーンショット付きで詳しい
動作確認
- curl コマンドで動作確認を行う
ユーザーの作成
$ curl -F "user[email]=user1@example.com" -F "user[password]=password" http://127.0.0.1:3000/users
アクセストークンの取得
- client_id には「Doorkeeper の認証用キーの作成」で作成した Application Id を、client_secret には同じく Secret を指定する
$ curl -F "grant_type=password" \ -F "client_id=07461c1f68870ea090ed20af49d0680782950119213e2a1af9e1f653c688dca3" \ -F "client_secret=20dea83f022c9e2a0e6fbec1da0e9c0e20c40e1bf09d2e8db029b461653a88bd" \ -F "username=user1@example.com" -F "password=password" \ http://127.0.0.1:3000/oauth/token.json
ユーザーの取得
- Bearer 以降には「アクセストークンの取得」で取得したトークンの文字列を指定する
$ curl -H "Authorization: Bearer 18afa04c80fbe3528b5d495c24e8badcbaeee12b2866d22d8f220c44543ae01c" http://127.0.0.1:3000/users/1
テスト
rspec の準備
$ rails g rspec:install
rails_helper.rb に追記
config.before :suite do DatabaseRewinder.clean_all end config.after :each do DatabaseRewinder.clean end config.before :all do FactoryGirl.reload end
specs_helper.rb に追記
require 'factory_girl_rails' config.include FactoryGirl::Syntax::Methods
factories/users.rb の作成
FactoryGirl.define do factory :user do sequence(:email) {|n| "user#{n}@example.com" } password "password" end end
factories/doorkeeper.rb の作成
FactoryGirl.define do factory :access_token, class: Doorkeeper::AccessToken do sequence(:resource_owner_id) { |n| n } application expires_in 1.hours end factory :application, class: Doorkeeper::Application do sequence(:name){ |n| "Application #{n}" } redirect_uri 'https://example.com/callback' end end
spec/controllers/user_controller_spec.rb
require 'rails_helper' RSpec.describe UsersController, type: :controller do describe "GET #show" do let!(:application) { create :application } let!(:user) { create :user } let!(:token) { create :access_token, :application => application, :resource_owner_id => user.id } it "returns http success" do get :show, {format: :json, access_token: token.token, id: user.id} expect(response).to have_http_status(:success) end end end
さいごに
- クライアントからは Doorkeeper のトークン情報を見れる必要はないから、中継用のアクションとかを用意したほうがいいかな?
Rails初期化チートシート
プロジェクトの作成
rails new PROJECT_NAME -T -B cd PROJECT_NAME
git の設定
git init
curl https://www.gitignore.io/api/rails,ruby,osx,linux,vim,sublimetext,rubymine > .gitignore
gemのインストール
cp /path/to/Gemfile ./ #TODO bundle install
bootstrap の適用
rails g layout:install bootstrap3
font-awesome の使用
vi app/assets/stylesheets/framework_and_overrides.css.scss @import "bootstrap-sprockets"; @import "bootstrap"; +@import "font-awesome";
simple_form
rails g simple_form:install --bootstrap
devise
rails g devise:install
rails g devise user
rake db:migrate
devise の日本語対応
rails g devise:views:locale ja rails g devise:views:bootstrap_templates
日本語化
curl -L https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml > config/locales/ja.yml
アプリケーション設定
vi config/application.rb
config.time_zone = 'Tokyo' config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] config.i18n.default_locale = :ja config.generators do |g| g.javascripts false g.stylesheets false g.helper false g.test_framework :rspec, fixture: true, view_specs: false, helper_specs: false, routing_specs: false, controller_specs: true, request_specs: true g.fixture_replacement :factory_girl, dir: 'spec/factories' end
devise 用のリンクを app/views/layouts/_navigation_links.html.haml に追加
- if user_signed_in? %li= link_to current_user.email, root_path, :class => 'navbar-link' %li= link_to 'Edit profile', edit_user_registration_path, :class => 'navbar-link' %li= link_to "Logout", destroy_user_session_path, method: :delete, :class => 'navbar-link' - else %li= link_to "Sign up", new_user_registration_path, :class => 'navbar-link' %li= link_to "Login", new_user_session_path, :class => 'navbar-link'
kaminari
rails g kaminari:config rails g kaminari:views bootstrap3
rspec
rails g rspec:install
テストの設定
spec/rails_helper.rb
RSpec.configure do |config| config.before :suite do DatabaseRewinder.clean_all end config.after :each do DatabaseRewinder.clean end config.before :all do FactoryGirl.reload end end
spec/spec_helper.rb
require 'factory_girl_rails' config.include FactoryGirl::Syntax::Methods
spec/factories/users.rb
actoryGirl.define do factory :user do sequence(:email) { |i| "user#{i}@example.com" } sequence(:password) { |i| "password#{i}" } end end
.pryrc の作成
begin require 'hirb' rescue LoadError # Missing goodies, bummer end if defined? Hirb # Slightly dirty hack to fully support in-session Hirb.disable/enable toggling Hirb::View.instance_eval do def enable_output_method @output_method = true @old_print = Pry.config.print Pry.config.print = proc do |*args| Hirb::View.view_or_page_output(args[1]) || @old_print.call(*args) end end def disable_output_method Pry.config.print = @old_print @output_method = nil end end Hirb.enable end
erb2haml
rake haml:replace_erbs
確認用ページ作成
rails g controller welcome index
figaro
bundel exec figaro install vi config/application.yml figaro heroku:set -e production