2013年2月28日木曜日

Twitterのbootstrapを使ってみるその1 ~マニュアル読み~

witter bootstrapのモーダルを使おうというはなし。
英語のマニュアルを難なく読める人なら困らんでしょうが、ワタクシの用に困る人もいるかも知れないので、導入メモを書いておきます。
全三回くらいで以下の流れで掲載します(予定です)。
  1. マニュアルをよむ
  2. とりあえずつかってみる
  3. カスタマイズしてみる
というわけで今回はマニュアルを読むはなし。

まず、オフィシャルのマニュアル。
ココにサンプルも使い方もあるので、正直このとおりにやればまず大丈夫なはずなので、『なんだ これがあれば大丈夫だ』と言う人はココで読むのをやめて美味しいビールでも飲みに行くのが良いと考えます。

続けます。
で、マニュアルこの部分の構成は。
Modals bootstrap-modal.js
Excample
     static example
     live demo
Usage
     Via data attribute
     Via JavaScript
     Options 
Methods
Events

となっているので(エキサイト)邦訳すると以下
『モーダル ~bootstrap-modal.jsで実現するエレガントなモーダル~』
サンプル
     静的なサンプル(ソースと静的な画面表示を見せるよ)
     ライブデモ(動きのあるデモを見せるよ しかもソース付きだ)
使い方
     (HTMLの各フォームの)属性で実現する場合
     JavaScriptで実現する場合
     オプション
(このモーダルを利用する際のJavaScriptの)メソッド
(このモーダルで発生する)イベント

の感じになる。

さしあたり動かして気分良くなりたいので、以下のソース

Launch demo modal





を貼りつけてみる。


で表示してみると以下のような感じ。(青字の説明はコッチで入れた) 
#ムロンbootstrap.js(全部入り)とかbootstrap.cssはインクルード済の実験画面



うむ、コレだけでもだいぶいいな、と思いつつ以下の修正をしたい。
  1. 挙動制御はデータの属性ではなくJavaScript制御にしたい
  2. モーダル起動のフォームがリンクになっているのでボタンにしたい。
  3. モーダルの中身を修正したい
  4. ボタンのテキストを変えたい
  5. ajax通信でフォームを投稿したい
というわけでやっていくことにする。
#5以外はだいたいだいじょうぶなはず。

1. 挙動制御をデータの属性ではなくJavaScript制御にする。
まあ気分的な問題もあるのですが、属性でやられると『なんでこうなるんだっけ?』になったり、『間に処理挟みたいんだけど…』とかになったりするので、やっておきます。

そのためには以下を確認しておきましょう。
  • JavaScriptでモーダルを実現する方法(やりたいことなので)
  • データ属性でモーダルを実現する方法(現在の状態から何を消せばいいかを明らかにするため)
【JavaScriptでモーダルを実現する方法
マニュアルに
とありますので、訳すまでも無いけど以下の感じ。
『1行のJavaScriptでモーダルメソッドをid="myModal"に対して呼び出しましょう。』
うむ、これはあれだ、下手に翻訳するよりコードを見せた方が早いというやつだ。
要は、『モーダルとして使用するdivのidを指定してjQueryオブジェクトを取得して、モーダルメソッドを呼びなさい』、だね。
#多分特定(セレクタで一意に抽出)出来るなら id じゃなくclassとかでもいいんだと思うけど、まあidセレクタが無難でしょう。

あとこれdivじゃなくてspanとかformとかで出来るのか気になるけど、それはまた機会があったら実験して見ることにしよう。
#全体を囲ってCSSのdisplay:noneを適用できるのが、多分divだけなのでdivなんだと思うけど。

データ属性でモーダルを実現する方法
マニュアルに


とあるので、さっきのよりは訳しがいがあるので訳してみると以下の感じ。
JavaScriptを使わずにモーダルを有効にする場合、コントローラ要素(ボタンとか)に data-toggle="modal" という属性を追加します。
さらにターゲットのモーダルを指定するのには以下のどちらかの属性を追加します。
  • data-target="#foo"
  • href="#foo"
なのでコントローラ(モーダルを立ち上げるボタンとか)側にdata-toggle と [data-target="{モーダルのdivのid}" or href="{モーダルのdivのid}"]
を指定すればいいらしい。なーる。

あとはモーダルに対するオプションについて

という感じなのでついでに邦訳してみると以下の感じ。

オプション
オプションはデータ属性またはJavaScriptで指定することができます。
データ属性で指定する場合は data-{属性名} のようにして指定します。
レとしてbackdropオプションを指定するのであれば data-backdrop="" という形式でコントローラ要素に追加します。

backdropオプション
⇒ よくわからんが、多分『trueを指定するとモーダルの背景クリック時にモーダルを閉じる、さもなくば閉じない(なにも起きない)、そしてdefaultはtrue』ということだと思う。
あれ? 
さっきのお試し画像の青字で『周囲はグレーアウトされてて操作できない』って書いてなかったっけ?
うん、試してみたらできた、ゴメン。大人はウソツキではないのです、間違いを犯すだけなのです。

keybordオプション
⇒escキーでモーダルを閉じる、defaultはtrue。

showオプション
⇒初期化時にモーダルを開くか、defaultはtrue。これはよくわからん。初期化=画面描画ならいきなり立ち上がるってこと? でもその挙動はしていないので、『modalメソッドの呼出=初期化』でありそのmodalメソッドの呼出時に起動するかどうか、ということだろうか。
うーん。
remote
⇒リモートurlを指定すると、jQueryでajax通信をして取得できた中身を.modal-bodyに展開する。ベンリ。そしてなぜこれをtabAPIでやってくれない。

というあたりで今回はコレぐらいにします。
マニュアルの残りは
  • Method
  • Event
なのでやりながら見ていくとして、次回は実際に動かしたり、自分用のモーダルを作ったりしてみます。

2013年2月20日水曜日

Rubyのeachの中でインデックス番号を扱う

よくあるネタ。
知らない人(というかチョット前の自分)はこんなふうに書いてるはず。
i = 0
goldsaints.each do |goldsaint|
    # ここは第2の宮 そしておれはカプリコーンのアルデバランだ のようなメッセージを出力する
    p 'ここは第' + (i + 1).to_s + 'の宮 そしておれは' + goldsaint.asterism + 'の' + goldsain.name + 'だ'
    # (エレガントでない)インデックスインクリメント
    i++
end

しかしいつものことだけど、『外側でローカル変数を宣言して中でインクリメントして…』的なやり方は実にださい、しかしeachはベンリなので使いたい、とかいう流れになる。
# javaでも拡張forループはベンリだけどindexが必要になる場合は普通のforループとか使ってた。

で『rubyならそのへん上手くやってくれるんじゃない?』と思い先生に『ruby each index』とか聞いてみたところ、『each_with_index』なるメソッドがあるらしい。

うむ、コレを使えばブロックの変数宣言のところで
|element, i| 的にして中でiが使えるらしい。
となるとさっきのコードは

goldsaints.each_with_index do |goldsaint, i|
    # ここは第2の宮 そしておれはカプリコーンのアルデバランだ のようなメッセージを出力する
    p 'ここは第' + (i + 1).to_s + 'の宮 そしておれは' + goldsaint.asterism + 'の' + goldsain.name + 'だ'
end
となる。
ちょっとエレガントになった、しめしめ。

2013年2月14日木曜日

railsでDBのトリガーっぽいことをやる

やりたい事は以下の
『モデルAを作成(DBにレコードを登録)した際に、別のモデルも登録する』
みたいなの。

抽象的に書くと分からんので具体例にすると『全種類合わせた商品購入履歴を登録する』みたいなやつ。
例としては、ぶき・ぼうぐ・どうぐの購入履歴テーブルがあった時に、全部の購入履歴をと管理するようなテーブルを同時に用意しておくみたいなやつ。

こんなもん『SQL結合すれば専用テーブル作らんでもいいだろ』というはなしもあるのですが、以下のメリットがあるので作ることにしました。

  • 複雑なSQLを書かなくてて済む
  • 履歴⇒履歴詳細データという辿り方が簡単にできる(前購入履歴には各履歴への参照キーを持たせる想定)


よくある話なのでWeb上にもそこら中に情報が載ってますが、備忘録として書いておきます。
#railsはモデルの関連性が簡単にかけるのでこの手の話はすぐできる。
#あと、そもそももっと上手くやる方法があるのかもしれん風来のシレン。

整理すると

  • やりたいこと:あるレコード新規登録時に別のテーブルにもレコードを登録するようにする
  • 実現の基本方針:(きっと)railsのActiveRecordにはいい感じのメソッドがあるのでそれでやる


で、以下のようにした。
# 武器購入履歴モデル
class BukiKounyuRireki < ActiveRecord::Base
  # 以下の情報を持つ
  # 購入した武器のID
  # 購入者の名前
  # 個数
  attr_accessible :buki_id, :user_id, :count

  # レコード登録時に発動するコールバックafter_createメソッドを定義
  # |record| は登録した元レコードの中身が入っている
  after_create {|record|
    # 前購入履歴テーブルにレコード(購入者、武器購入履歴ID[武器IDではない],購入物タイプ)を登録する
    AllRireki.create user_id: record.userid, rireki_primary_id: record.id, item_type: 1
  }
end

サマリーすると、

  • after_createメソッドを使って定義します
  • フック元のレコードはafter_create のブロック内で宣言した第一引数として使用できます(正確な説明ではない可能性が高い…)

ということです。

↑のモデル関連だとあんまり力を発揮しませんが、履歴の幅を広げて、
どんな道具を使ったか記録したい
誰と話したか記録したい
とか言うように、元履歴のテーブルの構造がバラバラになれば今回のようなモデル(全履歴管理モデル)がパワーを発揮してくる(とおもう)。

2013年2月9日土曜日

deviseのヘルパーメソッド(user_signed_in?,current_user,user_session)について調べてみたその2 ~実行してみる編~


前回に引き続きdeviseのヘルパーメソッド(user_signed_in? ,current_user,user_session)について実際に実行して調べてみます。

ちなみに読むのがめんどい人は以下の結論


user_signed_in?:
操作してる人がログインしてればtrue、してなきゃfalse。
他の人のログイン状態とかは関係ない。

current_user:
操作している人のユーザ情報(deviseの認証につかってるやつ)がとれる。
ログインしてなきゃnil。
ムロン他の人のは取れない。

user_session:
初期状態は空っぽ。
(おそらく)ユーザごとのセッションデータのいれもの。


だけ目を通してマンガ喫茶に行ってジョジョの奇妙な冒険でも読むのがいいと思います。

以下のようなコードを書いておき
    logger.debug 'ここからテスト用ログ'
    result = user_signed_in?
    logger.debug 'result is ' + result.to_s

    login_user = current_user
    if login_user
      logger.debug 'current user email is ' + login_user.email.to_s
    else
      logger.debug 'current user is nil'
    end

    # 現在のログインユーザ情報を取得
    login_user = user_session

    # 一応nilチェック
    if login_user
      logger.debug 'login_user :' + login_user.to_s
    else
      logger.debug 'login_user is nil'
    end
    logger.debug 'テスト用ログここまで'



以下のようなパターンで出力させてみる。
  1. 誰もログインしていない場合
  2. test1ユーザだけがログインしている場合でそのセッションを張っているブラウザからアクセス
  3. test1ユーザだけがログインしている場合でそのセッションを張っていないブラウザからアクセス
  4. test1,test2ユーザ二人がログインしている場合に各々のセッションを張っているブラウザからアクセス
このうち3,4については同じ種類のブラウザだと、
『セッションが共有されて上手くテストができない!』*1
とか言うケースも考えられるのでchromeとFireFox2つのブラウザから試して見ることにした。
で、結果は以下。
1. 誰もログインしていない場合
ここからテスト用ログ
result is false
current user is nil
login_user is nil
テスト用ログここまで

2.test1ユーザだけがログインしている場合でそのセッションを張っているブラウザからアクセス
ここからテスト用ログ
  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
result is true
current user email is test1@aa.bb
login_user :{}
テスト用ログここまで

3.test1ユーザだけがログインしている場合でそのセッションを張っていないブラウザからアクセス
ここからテスト用ログ
result is false
current user is nil
login_user is nil
テスト用ログここまで

4. test1,test2ユーザ二人がログインしている場合に各々のセッションを張っているブラウザからアクセス
From test1
ここからテスト用ログ
  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
result is true
current user email is test1@aa.bb
login_user :{}
テスト用ログここまで

From test2
ここからテスト用ログ
  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
result is true
current user email is test2@aa.bb
login_user :{}
テスト用ログここまで

という結果になった。
まとめると。
user_signed_in?:
リクエストを発行したブラウザがログインしていない場合はfalse(他に誰がログインしていようが関係ない)
リクエストを発行したブラウザがログインしている場合はtrue

current_user:
リクエストを発行したブラウザがログインしていない(ログイン済セッションを張っていない)場合はnilが返る。
リクエストを発行したブラウザがログインしている(ログイン済セッションを張っていない)場合は、『発行ブラウザでログインしたユーザの情報のみ』が取得できる。

user_session:
常に空っぽ。
まあこれはなんにも詰めていないからだと推測できる。

ということになり、いわゆる基本的なログイン情報の取得(既にログイン済なのか、ユーザ情報はなんだっけ)に使えそうなメソッドであることがわかった。
よかったよかった。


1:          ,. -‐'''''""¨¨¨ヽ
         (.___,,,... -ァァフ|          あ…ありのまま 今 起こった事を話すぜ!
          |i i|    }! }} //|
         |l、{   j} /,,ィ//|       『おれは新しいウィンドウでログインしようと
        i|:!ヾ、_ノ/ u {:}//ヘ        思ったらいつのまにかログインしていた』
        |リ u' }  ,ノ _,!V,ハ |
       /´fト、_{ル{,ィ'eラ , タ人        ど… どういう機構なのか わからねーと思うが
     /'   ヾ|宀| {´,)⌒`/ |<ヽトiゝ        おれも何をされたのかわからなかった…
    ,゙  / )ヽ iLレ  u' | | ヾlトハ〉
     |/_/  ハ !ニ⊇ '/:}  V:::::ヽ        セッション管理がどうにかなりそうだった…
    // 二二二7'T'' /u' __ /:::::::/`ヽ
   /'´r -―一ァ‐゙T´ '"´ /::::/-‐  \    キャッシュだとかリバースプロキシだとか
   / //   广¨´  /'   /:::::/´ ̄`ヽ ⌒ヽ    そんなチャチなもんじゃあ 断じてねえ
  ノ ' /  ノ:::::`ー-、___/::::://       ヽ  }
_/`丶 /:::::::::::::::::::::::::: ̄`ー-{:::...       イ  もっと恐ろしいものの片鱗を味わったぜ…

2013年2月8日金曜日

deviseのヘルパーメソッド(user_signed_in?,current_user,user_session)について調べてみたその1 ~API読む編~

今回はdeviseのヘルパーメソッドメソッドについて調べてみます。

事の発端は
はて、devise使ってる時にログインしているユーザのIDとか名前とかセッションに入ってるんだっけ?
⇒入ってるならどうやって取るんだっけ?
⇒なんかそういうヘルパーメソッドあるんじゃなかったっけ?
⇒そもそもそのヘルパーメソッドって何なんだっけ?
というあたりでした。

というわけでdeviseのヘルパーメソッド
  • user_signed_in?
  • current_user
  • user_session
について見てみることにします。

知っておきたいのは以下のあたり。
  • 仕様(何を入れたら何が返るか)
  • どこにいるのか(どのクラスにいるのか)
  • どう使えばいいか
というわけでサーフィンしに行ったですが、探し方が悪いのかあんまりいい感じの結果は出てきませんでした><
せいぜい以下のカンジ。
  • 3つのヘルパーメソッドがあります
  • current_userを使うと現在のユーザ情報が取れます
  • ログインチェックにはuser_signed_in?を使います。
となればオフィシャル(?)ドキュメントを見るしかなかろう、ということで
を見に行きました。

フレーム左部分の

Classes | Methods | Files 
からMethodを選択して、『user_signed_in?』とかで探してみるとなんもない。
うむ、自動生成されてるからプレフィックスがいかんのかもしれんというわけで『signed_in』とかで探してみると…

あったあった、よかったよかった。

で内容は

- (Booleansigned_in?(scope = nil)

Return true if the given scope is signed in session. If no scope given, return true if any scope is signed in. Does not run authentication hooks.
Returns:
  • (Boolean)
とかなので、(いんちき)翻訳すると。

入力:scope(なくてもいい)
出力:
引数で渡されたスコープがログイン済である(is signed in session) or スコープが渡されていない場合は任意のスコープでログインしている ⇒ true
false:それ以外
説明:本メソッドは認証フックを発生させない。

うーん、大体ニュアンスは分かるのだが、スコープという単語の意味がわからないのでメソッドの↑の方を探してみたところ。
Define authentication filters and accessor helpers based on mappings. These filters should be used inside the controllers as before_filters, so you can control the scope of the user who should be signed in to access that specific controller/action. Example:
Roles:
  User
  Admin

Generated methods:
  authenticate_user!  # Signs user in or redirect
  authenticate_admin! # Signs admin in or redirect
  user_signed_in?     # Checks whether there is a user signed in or not
  admin_signed_in?    # Checks whether there is an admin signed in or not
  current_user        # Current signed in user
  current_admin       # Current signed in admin
  user_session        # Session data available only to the user scope
  admin_session       # Session data available only to the admin scope

Use:
  before_filter :authenticate_user!  # Tell devise to use :user map
  before_filter :authenticate_admin! # Tell devise to use :admin map
とか書いてあるので、userスコープ(多分一般ユーザ)とadminスコープ(多分管理者ユーザ)の事だと思う。

つまり使い方としては、userスコープかadminスコープかもしくは何も指定しないで実行すると、該当のスコープに所属するユーザのセッションが張られている(ログインしている)かどうかを判別できる、というカンジだと思う。

なーる。
では current_user は? と思ってさっきのフォームから探してみたのだが無い。
と思ったけど、↑のやつにcurrent_user っているぞ? どうなってんだ?
で、チョットそこを読んでいくと下の方に
というリンクが。
展開してみると。
# File 'lib/devise/controllers/helpers.rb', line 42

def self.define_helpers(mapping) #:nodoc:
  mapping = mapping.name

  class_eval <<-methods data-blogger-escaped-1="" data-blogger-escaped-:="" data-blogger-escaped-__file__="" data-blogger-escaped-__line__="" data-blogger-escaped-authenticate_="" data-blogger-escaped-current_="" data-blogger-escaped-def="" data-blogger-escaped-devise_controller="" data-blogger-escaped-end="" data-blogger-escaped-force="" data-blogger-escaped-if="" data-blogger-escaped-mapping="" data-blogger-escaped-opts.delete="" data-blogger-escaped-opts="" data-blogger-escaped-scope="" data-blogger-escaped-signed_in="" data-blogger-escaped-warden.authenticate=""> :#{mapping})
    end

    def #{mapping}_session
      current_#{mapping} && warden.session(:#{mapping})
    end
  METHODS

  ActiveSupport.on_load(:action_controller) do
    helper_method "current_#{mapping}", "#{mapping}_signed_in?", "#{mapping}_session"
  endend


と出てきた。
define_helpers とあるのでそのものズバリメソッドを自動生成(生成という単語は適切では無い気がするが)するメソッドなのだろう。
で見るとたしかに

#{mapping}_signed_in?
current_#{mapping}
#{mapping}_session

という3つのメソッドがあるため、探したかった
  • user_signed_in?
  • current_user
  • user_session
が自動生成されるのだと読み取れる。
が、これ以降のコードの読み解きは現段階ではムズカシイので一旦パスすることにする。
#なんか↑のコード @current_#{mapping} ||= warden.authenticate(:scope => :#{mapping})
だと、ログインしているユーザ全部とれちゃいそうな気がするのだが。。。

というわけで次回は百聞は1実行に如かずということで実際に打ってためしてみよう。
一旦以上。

2013年2月6日水曜日

deviseの標準画面をカスタマイズする ~標準画面をカスタマイズする その2~

今回は前回作ったこれ


をこう


しようという話です。
#チョットきれいになります。

なんかはじめにやりたかったことは以下のあたりです。

  • 見出し(ログインという文字)のフォントをメイリオ(最近気に入ってます)にしよう
  • 見出しは中央寄せがいいなぁ
  • 見出しとフォームの間に仕切り線が欲しいなあ
  • ログインのフォームはは中央寄せがいいなあ
  • ボタンはカッコいいのがいいなぁ

で、結果的に↑のようになりましたので、その流れを見ていきます。

まず見出しものについてですが、これはまあそんなに難しくはなく以下のカンジだろうと思ってました。

  1. とりあえず見出し部分はdivで囲もう
  2. divの中身をtext-align:center;とかすればいいだろう
  3. h2のフォントでも指定すればいいだろう
  4. 見出しの下にhrでもつければいいだろう

というわけでソース(html.erb)を以下のように修正。
#といってもdivで囲ってその下にhrつけただけ。

ログイン


これに対して以下のCSS(railsなのでscss記法です)を当てると見出し部分ができます。
  // 画面全体の背景色
  background-color: #f5f5dc;

  // タイトル部分のスタイル指定
  .page-title {
    text-align: center;
    h2 {
      font-family: 'メイリオ',Meiryo,'MS Pゴシック',sans-serif;
    }
  }

次にログインフォームの部分。
これも以下のカンジで1つ1つ解決しました。

  1. 角丸・白抜きにするためにフォーム全体をdivで囲ってwidthを指定しよう(widthを指定しないと角丸が画面全体になってしまう)
  2. 角丸はborder(仕切り線)とborder-radiusでやればいいや
  3. ラベルとテキストボックスとボタンはpadding-leftでいい感じの位置になるようにしよう
  4. bootstrapにはかっこいいボタンのクラスが用意されていたからそれを当てよう

というのは虚偽報告で、実際はchromeのdeveloper tool*1使いながらガチャガチャCSS当てて行ってなんとか今の形にしました。
#レイアウティングやCSS当てが得意な人ならいきなり最終型にいけるんだろうが、ワタクシにはそのような力はありません。

というわけでソースを以下のように修正。
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<%= f.label 'メールアドレス' %> <%= f.email_field :email, :autofocus => true %>

<%= f.label 'パスワード' %> <%= f.password_field :password %>

<%= f.submit "ログイン", {class: 'btn btn-primary'} %>
<% end %>

ボタンをかっこ良くするにはsubmitボタンのclassに 'btn btn-primary' を指定することで実現しています。
#bootstrapにはボタンをかっこ良くするためのCSSクラス定義が既に準備されている。

これに対して以下のCSSを当てるとログインフォーム部分ができます。
  // ページコンテンツ(ここではログインフォームのみ)のスタイル指定
  .page-contents {
    // ログインフォームのスタイル指定
    #div-login-form {
      // サイズ系指定
      width:280px;
      margin:10px auto;
      padding-top: 10px;

      // フォーム全体の背景色(白抜き部分)
      background-color: window;

      // ------フォームを囲っている角丸の部分ここから------
      // 角丸の線スタイル(太/色/線種)指定
      border: 5px solid #002a80;

      // 角丸表現(クロスブラウザ対応のため一応3パターン指定)
      border-radius: 5px;
      -webkit-border-radius: 5px;
      -moz-border-radius: 5px;
      // ------フォームを囲っている角丸の部分ここまで------

      // フォーム内のコミットボタン以外は左端から30pxとする
      div {
        padding-left: 30px;
      }

      // フォーム内のコミットボタンだけは左端から100pxとする
      // #class指定がform種別指定より優先度が高いので有効になる
      .div-commit-button {
        padding-left: 100px;
      }
    }

そこそこきれいな画面ができたので今回のdeviseの標準画面をきれいにするシリーズは一旦以上とします。
#そこそこ綺麗な画面ができたので次回は別の話をしようかと。

1:現在のドキュメントの構造を確認したりCSSの適用状態を確認したりJavaScriptのデバッグを確認したりできる、とんでもなくすばらしいツール。

2013年2月5日火曜日

deviseの標準画面をカスタマイズする ~標準画面をカスタマイズする~

というわけで今回はdeviseの画面をカスタマイズします。
カスタマイズ手順にありがちな以下の感じでやっていきます。
  1. 現在の画面の仕様を理解する(知らずににいじると不完全な機能をもった画面になるので)
  2. 現在の画面をバックアップする(この辺はgitでも使ってりゃいらないんだろうけど、とりあえず今回はやっておきます)
  3. どういうカスタマイズをするか決める
  4. カスタマイズする

1. 現在の画面の仕様を理解する。
ざっくり理解(というかそんなに難しくない)します。
前にやってきた標準画面の整理の話とソースをみると以下の点がわかる。
画面遷移については以下
  • ログインフィルターでNGが出るとこの画面に飛んでくる
  • ここからログイン処理を呼び出してOKだと標準画面(root_to で指定する所)に飛ぶ
  • この画面からユーザ登録画面(SignUp)に飛べる
  • この画面からパスワードリセットメール発行画面に飛べる
画面の項目については以下
ログイン用フォーム
  • email入力フォーム
  • password入力フォーム
  • remember_meチェックボックス
リンク
  • ユーザ新規登録へ
  • パスワードリセットメール発行へ
ポイントとしては以下2点だと考えます。

  • フォームの飛び先(ひじょうにきほんてきですが)
  • リンクを出力しているコード

フォームの飛び先と何をPOSTするかを理解していれば自動生成されたViewを使わずとも、自分でログイン用フォームを作れるでしょう。

あと、リンクを出力する部分はerb中で
<%= render "devise/shared/links" %>
のように部分テンプレートで描画している。
⇒ つまり片方のリンクだけ消すとかいうのは(さしあたり)ムリなように見える。

ちなみに該当の部分テンプレートの中身は以下のカンジ。
#毎度のごとく日本語コメントはコチラで追加。

<%- if controller_name != 'sessions' %>
  
  <%= link_to "Sign in", new_session_path(resource_name) %>

<% end -%>



<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
  
  <%= link_to "Sign up", new_registration_path(resource_name) %>

<% end -%>



<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
  
  <%= link_to "Forgot your password?", new_password_path(resource_name) %>

<% end -%>


<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
  <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>

<% end -%>




<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
  
  <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>

<% end -%>



<%- if devise_mapping.omniauthable? %>
  
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>

  <% end -%>
<% end -%>


のように『現在のユーザの状態を判断して必要そうなリンクを出力する』のようになっているので、標準以外で行くならココの部分の必要なリンクだけ抜き出すのがいいでしょう。

というわけでざっくり以下の理解と課題抽出ができました。

  • ログインフォーム(formの部分ね)のPOST先と中身
  • リンクを出力する機構
  • confirmable,lockable,omniauthableが示す機構については調べないと


2. 現在の画面をバックアップする
ホントgitを使えばこんなのいらなくて、こんなことをやっているのがバレたら、
『アムロ! バージョン管理ツールを使わずにファイルの手動バックアップを繰り返す人類など、システム開発のノミだということがなぜ分からん!』
とか罵られて
アクシズを落とされても文句が言えないレベル
だと思うのですが、さしあたりできてないのでカッコ悪いバックアップします。
ログイン画面を表示しているのは
{railsアプリルート}/views/devise/sessions/new.html.erb
なので、同じ場所に new.html.erb.bcup という名前でバックアップしておきます。
#ださい。。。

3. どういうカスタマイズをするか決める
難しいカスタマイズは後回しにしてまずは簡単なところから行きましょう。
というわけで以下のカンジ。

  • 表示内容を日本語化する
  • rememberチェックボックスはよくわからないから一旦外す
  • ユーザ登録とパスワードリセットのリンクも一旦外す(遷移を封印して管理対象を少なくするのがねらい)


4. カスタマイズする
↑のカスタマイズをしていきます。
まずは簡単なリンクの封印から。
これは↑で調べておいた通りリンクを描画する部分テンプレートを封印すればいい。

次に表示内容の日本語化。
これはページタイトルと入力フォームの部分を日本語化すればいい。

最後にrememberチェックボックスを外すのは、何も考えずに一旦コメントアウトするという作戦で行く。
#パラメータが投稿されてないからエラーになるとかいう可能性があるけど、一旦やってみる。
で、できたのがコレ。


ちなみにソースはこう。

ログイン

<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<%= f.label 'メールアドレス' %>
<%= f.email_field :email, :autofocus => true %>
<%= f.label 'パスワード' %>
<%= f.password_field :password %>
<%= f.submit "ログイン" %>
<% end %>
という感じで当初の目的は達成したので、今回は一旦以上。
次回は余裕があればもうチョットいじってみようかと思います。





2013年2月4日月曜日

deviseの標準画面をカスタマイズする ~bootstrapを当ててみる~


この記事金曜に投稿しようと思ってたのですが、ジョジョ見てたら忘れてました。
#きっとキング・クリムゾンの攻撃をうけたのでしょう。

で、今回から数回はdeviseで自動生成された画面をチョットカスタマイズしましょう、という話。
まずは、はやりのTwitter bootstrapを当ててかっこ良くしようと考えています。
というわけで以下。

1. bootstrapについて
この後bootstrapについて自分の理解を書いています。

Twitter bootstrapとも。
ざっくり言うと、
『見栄えがよくてかっこいい動きをするサイトをつくるためのCSS(ちょっとJavaScript)フレームワーク』
ということだと思う。
じゃ、『フレームワークってなんだよ』、という話ちょっと扱いを間違えると宗教戦争的な発展を見せるので、今はざっくり
『ある目的を達成をしようとする場合は、その目的を達成するのに特徴的な行為とその目的でなくても必要となる一般的な行為を合わせて実行することが多い』
『そのため特徴的な部分の実行に注力できるように、一般的な行為の代行をすると共に行為全体の実行順序制御御を代行してしまう枠組みが効果的である』
『このような枠組みをフレームワークと呼ぶ』
ということでおいておいてください。
#ムロンざっくりで無いことは認識済。

で、↑の定義を今回の話に置き換えてみると、サイトの見栄えを良くするためには大体以下のことをやるはず。
  • かっこいいよく見せるためのCSSの定義(色/フォント/形/背景)
  • フォームにclass属性やid属性の追加
  • JavaScriptでイベントをハンドリングして動的にCSSが適用されるようにする
最終型がファンシーなサイトになるにせよエロサイトになるにせよ、
『CSSを定義し、それが適用されるようにHTMLを記述し、また動的な表現ができるようにJavaScriptを記述する』
という行為は変わらないはず。

そこでCSSフレームワークbootstrapの出番となる。
bootstrapは以下のことをやってくれる。(キャッチーな機能を抽出してるので全部ではないです)
  • 簡単にグリッドレイアウトが組めるようなCSS定義
  • ラベルやボタンなどのフォームがきれいな見栄えになるようなCSS定義
  • 普通なら自分でCSSを定義して自分でJavaScriptを書く必要があるナビゲーションやタブなどが簡単につくれるようなCSS定義&JavaScript

こいつはベンリだ。しかもTwitterが使ってるものだしかっこよくなるのが目に見えてるぜ。
というわけで使ってみましょうというのが今回の話。(なんかやたら前置きが長くなったけど)
#ムロンスライドでも言ってるように、『Twtterみたいな見栄えになっちゃう』、というデメリットはあるが。

2. bootstrapの導入方法
からダウンロードする。
Download Compiledの方を落としてくる。
落としてきたzipファイルを解凍すると
css
img
js
というフォルダがあるはずなので。
コレをrailsのアプリに取り込めばいい。
のだが取り込み方にちとコツ(というか注意)がある。

まず、簡単なJavaScriptの方から。
これはほぼ何も考えずに
{導入しようとしているrailsのアプリフォルダルート}/app/assets/javascripts
においてしまえばいい。
application.jsで
require tree
しているならそれで読み込まれるし、それ外してコントローラ単位にレイアウト作ったりしてるならそこで
<%= javascript_include_tag 'bootstrap.js' %>

とかして読みこめばいい。

次にCSSと画像なのだが、
『こんなもんCSSをassets/stylesheetsにおいて画像をassets/imagesにおけばいいんだろ、ちょろいもんだぜハハハ』
とかやると失敗する。
理由は、
『スタイルシートの中で背景画像を定義する部分においてスタイルシートファイルからの相対パスで画像のURLを指定している』
ため。
bootstrap.css には
  background-image: url("../img/glyphicons-halflings.png");
のような背景画像を読み込む定義があるのだが、↑のようにurlを相対パス(../img・・・)で指定している。
そのため、さっきのような置き方(assets/imagesに配置)とかだとパスが合わなくて画像が読み込めなくなる。

解決策としては以下2点となる。
  • cssの背景画像読込のURLを修正する
  • 画像の配置場所を修正する
で、どこをどう考えても後者の方がラクチンなのでそれを採用します。

で、『フォルダ構造とか一旦いいからとりあえず動かしてみたいよ』という人は
css
img
の二つのフォルダをassets/stylesheetsフォルダにコピーすればいいです。
あとはさっきのJavaScriptと同じように
application.css で
 *= require_tree
やってるなら(たぶん)読み込まれる。

各レイアウトで読み込むなら、
<%= stylesheet_link_tag    "css/bootstrap", :media => "all" %>
<%= stylesheet_link_tag    "css/bootstrap-responsive", :media => "all" %>
みたいなかんじでよいはず。
#今回はこっちでやった。

3. bootstrap当てたらどんな風になるか
beforeがこう。
#前にも出したやつ。

で、当ててみるとこうなった。

うむ、ぜんぜん違う。
パッと見でも

  • フォントが柔らかい
  • テキストボックスが角丸
  • リンクの色が違う
  • チェックボックスの横の文字列が改行してる
  • (あとこの画像だとわからないけど)テキストボックスにフォーカス入れると強調表示される

とかになっている。
次回はこの画面に対して

  • 日本語対応
  • レイアウト調整
  • 余裕があればbootstrapをチョット試してみる

とかをやっていきたい。
一旦以上。