Rails のルーティング

ルーティングを追加するとき、毎回調べているので少しずつまとめていこうかと思います。 等価な書き方が色々あるので絶対これというわけではありません。

scope

URI Pattern にのみ追加されます。

Rails.application.routes.draw do
  scope :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
       Prefix Verb   URI Pattern                      Controller#Action
profile_index GET    /user/profile(.:format)          profile#index
              POST   /user/profile(.:format)          profile#create
  new_profile GET    /user/profile/new(.:format)      profile#new
 edit_profile GET    /user/profile/:id/edit(.:format) profile#edit
      profile GET    /user/profile/:id(.:format)      profile#show
              PATCH  /user/profile/:id(.:format)      profile#update
              PUT    /user/profile/:id(.:format)      profile#update
              DELETE /user/profile/:id(.:format)      profile#destroy

scope module:

Controller#Action にのみ追加されます。

Rails.application.routes.draw do
  scope module: :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
       Prefix Verb   URI Pattern                 Controller#Action
profile_index GET    /profile(.:format)          user/profile#index
              POST   /profile(.:format)          user/profile#create
  new_profile GET    /profile/new(.:format)      user/profile#new
 edit_profile GET    /profile/:id/edit(.:format) user/profile#edit
      profile GET    /profile/:id(.:format)      user/profile#show
              PATCH  /profile/:id(.:format)      user/profile#update
              PUT    /profile/:id(.:format)      user/profile#update
              DELETE /profile/:id(.:format)      user/profile#destroy

scope as:

Prefix にのみ追加されます。

Rails.application.routes.draw do
  scope as: :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
            Prefix Verb   URI Pattern                 Controller#Action
user_profile_index GET    /profile(.:format)          profile#index
                   POST   /profile(.:format)          profile#create
  new_user_profile GET    /profile/new(.:format)      profile#new
 edit_user_profile GET    /profile/:id/edit(.:format) profile#edit
      user_profile GET    /profile/:id(.:format)      profile#show
                   PATCH  /profile/:id(.:format)      profile#update
                   PUT    /profile/:id(.:format)      profile#update
                   DELETE /profile/:id(.:format)      profile#destroy

scope と module: と as:

Prefix と URI Pattern と Controller#Action 全てに追加されます。

Rails.application.routes.draw do
  scope :user, module: :user, as: :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
            Prefix Verb   URI Pattern                      Controller#Action
user_profile_index GET    /user/profile(.:format)          user/profile#index
                   POST   /user/profile(.:format)          user/profile#create
  new_user_profile GET    /user/profile/new(.:format)      user/profile#new
 edit_user_profile GET    /user/profile/:id/edit(.:format) user/profile#edit
      user_profile GET    /user/profile/:id(.:format)      user/profile#show
                   PATCH  /user/profile/:id(.:format)      user/profile#update
                   PUT    /user/profile/:id(.:format)      user/profile#update
                   DELETE /user/profile/:id(.:format)      user/profile#destroy

namespace

Prefix と URI Pattern と Controller#Action 全てに追加されます。
( scope と module: と as: ) と同じ結果になります。

Rails.application.routes.draw do
  namespace :user do
    resources :profile
  end
end
➜  sample git:(master) ✗ be rake routes
            Prefix Verb   URI Pattern                      Controller#Action
user_profile_index GET    /user/profile(.:format)          user/profile#index
                   POST   /user/profile(.:format)          user/profile#create
  new_user_profile GET    /user/profile/new(.:format)      user/profile#new
 edit_user_profile GET    /user/profile/:id/edit(.:format) user/profile#edit
      user_profile GET    /user/profile/:id(.:format)      user/profile#show
                   PATCH  /user/profile/:id(.:format)      user/profile#update
                   PUT    /user/profile/:id(.:format)      user/profile#update
                   DELETE /user/profile/:id(.:format)      user/profile#destroy

Git で 変更を一時的によける

あるブランチで色々変更をしていて、中途半端だけど、急遽他のブランチで作業をしなければならなくなった時に、 git stash というコマンドが便利です。

変更を加える

➜  sample git:(hoge) touch hoge.txt
➜  sample git:(hoge) ✗ git add .
➜  sample git:(hoge) ✗ git commit -m "hoge.txt の作成"
➜  sample git:(hoge) hoge.txt の編集
➜  sample git:(hoge) ✗ git status
On branch hoge
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   hoge.txt

no changes added to commit (use "git add" and/or "git commit -a")

hoge.txt が作成されている hoge ブランチで hoge.txt を編集します。
git status でワークツリーに変更が加わっていることが確認できています。

変更をよける

ここで git stash を実行して、変更を一時的によけてみます。

➜  sample git:(hoge) ✗ git stash
Saved working directory and index state WIP on hoge: 2e894e0 hoge.txt の作成
HEAD is now at 2e894e0 hoge.txt の作成
➜  sample git:(hoge) git status
On branch hoge
nothing to commit, working directory clean

すると、git status を実行しても先ほどの hoge.txt の変更は表示されません。

一時的によけた変更は、 git stash show で確認できます。

➜  sample git:(hoge) git stash show

 hoge.txt | 1 +
 1 file changed, 1 insertion(+)

他のブランチで変更を加える

そして、fuga ブランチへ checkout して fuga.txt を作成し、これをコミットします。

➜  sample git:(hoge) git checkout fuga
Switched to branch 'fuga'
➜  sample git:(fuga) touch fuga.txt
➜  sample git:(fuga) ✗ git add .
➜  sample git:(fuga) ✗ git commit -m "fuga.txt の作成"
[fuga 199f6b5] fuga.txt の作成
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 fuga.txt

git show でコミット内容を確認してみても、hoge ブランチで行った作業の内容はコミットされていないことが確認できます。

➜  sample git:(fuga) git show

commit 199f6b526fc2b6612e31b8e913df2fe2ae4f0d42
Author: fuga <fuga@fuga.fuga>
Date:   Mon May 23 23:11:46 2016 +0900

    fuga.txt の作成

diff --git a/fuga.txt b/fuga.txt
new file mode 100644
index 0000000..e69de29

変更を戻す

次に、再び hoge ブランチへ checkout します。
先ほど一時的によけた変更を元に戻すには git stash pop を実行します。

➜  sample git:(fuga) git checkout hoge
Switched to branch 'hoge'
➜  sample git:(hoge) git stash pop
On branch hoge
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   hoge.txt

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (8e370d4ff891b72ee304fca8bca60b7371663fff)

念のため git status を実行してみると、変更が元に戻っていることが確認できます。

➜  sample git:(hoge) ✗ git status
On branch hoge
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   hoge.txt

no changes added to commit (use "git add" and/or "git commit -a")

TableViewController と Storyboard でテーブルをつくる (Swift)

ViewController でテーブルを作成する記事はよく見かけたのですが、 TableViewController を使う方法はあまりなかったのでメモ。

まだまだ学び始めたばかりでよくわかっていないことだらけなので、手順だけ。

ざっくり書くと、

  1. storyboard で TableViewController を追加
  2. 追加した TableViewController の cell に要素を追加
  3. TableViewController に対応させるファイル ( TableViewController.swift ) を作成
  4. cell に対応させるファイル ( TableViewCell.swift ) を作成
  5. cell と TableViewCell.swift を対応させる
  6. TableViewController と TableViewController.swift を対応させる
  7. TableViewController.swift を実装する

という流れです。

storyboard で TableViewController を追加

storyboard を選択し、右下のライブラリの検索窓に tableviewcontroller 等と入力して、 TableViewController をドラッグアンドドロップ

f:id:komaji504:20160516211526p:plain

追加した TableViewController の cell に要素を追加

先ほどと同様に、cell に追加したい要素をライブラリで検索します。
要素を cell の上にドラッグアンドドロップで配置します。

f:id:komaji504:20160516231901p:plain

TableViewController に対応するファイル ( TableViewController.swift ) を作成

command + N 等でファイル作成画面を開きます。
Cocoa Touch Class を選択します。

f:id:komaji504:20160516231929p:plain

次画面で、 Subclass of を UITableViewController にします。
Class 名は自由に変更して構いません。

f:id:komaji504:20160516231937p:plain

そのままファイルを作成します。

f:id:komaji504:20160516231952p:plain

cell に対応させるファイル ( TableViewCell.swift ) を作成

先ほどと同様に、 Cocoa Touch Class を選択します。

次画面で、 Subclass of を UITableViewCell にします。
Class 名は自由に変更して構いません。

そのままファイルを作成します。

f:id:komaji504:20160516232112p:plain

cell と TableViewCell.swift を対応させる

アイデンティティインスペクタから Custom Class の Class を TableViewCell にします。

f:id:komaji504:20160516234353p:plain

次に、属性インスペクタ から Identifier を任意の値に設定します。この値はファイル名と対応していなくて構いません。

f:id:komaji504:20160516234409p:plain

TableViewController と TableViewController.swift を対応させる

先ほどと同様に、 アイデンティティインスペクタから Custom Class の Class を TableViewController にします。

f:id:komaji504:20160516232210p:plain

ちなみに、ここまでの作業だけで storyboard で cell に追加した要素が表示されるようになるかと思うかもしれませんが、まだ不十分です。
Build には成功しますが、以下のよう cell には何も表示されないので、後述の実装が必須となります。

f:id:komaji504:20160516232241p:plain

UITableViewController.swift を実装する

たくさんコードが書いてありますが、変更する箇所は以下の関数 3 つです。

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 0
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return 0
}

/*
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)

    // Configure the cell...

    return cell
}
/*

この 3 つを以下のように書き換えるだけです。

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 5 // 任意の値に変更する ( 1 セクションあたりの行数 )
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return 1 // 任意の値に変更する ( セクション数 )
}

// コメントアウトを外す
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("TableViewCell", forIndexPath: indexPath) // reuseIdentifier を cell に設定した Identifier に書き換える

    // Configure the cell...

    return cell
}

以上で作業は終わりです。

確認する

Build すると追加した要素が表示されていることを確認できます。
レイアウトは調整していないので汚いです。

f:id:komaji504:20160516234517p:plain

モデルの属性に自作のバリデーションを追加する

モデルの属性のバリデーションをしたいときに、バリデーションヘルパーの既存バリデーションだけでは足りないことがあると思います。

そのため、Rails ではバリデーションを自作することができます。

その一つの方法としてバリデーションメソッドがあります。

バリデーションメソッド

バリデーション用のメソッドを定義して、オブジェクトの保存時にそのメソッドによりバリデーションする方法です。

例えば、 name 属性を持つ Account モデルに対して、 name の prefix として hoge_ があるというバリデーションをしたい (?) 場合には、以下のようにします。

class Account < ActiveRecord::Base
  validate :prefix_hoge # 保存時に prefix_hoge によりバリデーションをする

  # バリデーションメソッド
  def prefix_hoge
    if name !~ /^hoge\_/ # バリデーションの条件
      errors.add(:name, " は hoge_ から始まるようにしましょう!") # エラーメッセージ
    end
  end
end

これにより、 prefix として hoge_ のない name を持つ Account のオブジェクトを保存しようとするとエラーになります。

# hoge_ あり
irb(main):001:0> Account.first.update(name: 'hoge_test')
  Account Load (0.3ms)  SELECT  `accounts`.* FROM `accounts`  ORDER BY `accounts`.`id` ASC LIMIT 1
   (0.2ms)  BEGIN
  SQL (0.3ms)  UPDATE `accounts` SET `name` = 'hoge_test', `updated_at` = '2016-05-13 14:59:13' WHERE `accounts`.`id` = 1
   (0.6ms)  COMMIT
=> true

# hoge_ なし
irb(main):002:0> Account.first.update(name: 'test')
  Account Load (0.3ms)  SELECT  `accounts`.* FROM `accounts`  ORDER BY `accounts`.`id` ASC LIMIT 1
   (0.1ms)  BEGIN
   (0.2ms)  ROLLBACK
=> false

ですが、 複数の属性に対してバリデーションヘルパーとバリデーションメソッドの両方を利用すると、以下のように同一の属性のバリデーションの記述が分散してしまうことがあります。

class Account < ActiveRecord::Base
  validates  :name,
    presence: true,
    length: { maximum: 10 }
    
  validates :email,
    presence: true

  validate :prefix_hoge

  validate :email_validation

バリデーションを自作する他の方法として、カスタムバリデータがあります。 こちらを使えば上記の点を防ぐことができます。

カスタムバリデータ

先ほどと同様のバリデーションを追加します。
カスタムバリデータは、 バリデーションを自作するためのクラスを継承したクラスを作ります。
クラスには ActiveModel::Validator と ActiveModel::EachValidator の二種類があるのですが、後者の方が使いやすいと思うのでそちらについてのみ書きます。

クラスをパスの通っている場所に作成します。

# lib/validators/prefix_hoge_validator.rb

class PrefixHogeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value) # バリデーションメソッド
    if value !~ /^hoge\_/
      record.errors.add(attribute, " は hoge_ から始まるようにしましょう!")
    end
  end
end

バリデーションメソッドは必ず validate_each とします。

validate_each の引数の record に保存対象のオブジェクトが、 attribute にバリデーション対象の属性が、 value に保存する値 が渡ってきます。

これを Account モデルで利用する際には、バリデーションヘルパーのように記述することができます。

class Account < ActiveRecord::Base
  validates :name,
    prefix_hoge: true

これで先ほどと同様のバリデーションをすることができます。

カスタムバリデータであれば、複数の属性に対してバリデーションヘルパーとバリデーションメソッドの両方を利用したとしても、以下のように対象の属性の記述が一箇所にまとまり見やすくなります。

class Account < ActiveRecord::Base
  validates  :name,
    presence: true,
    length: { maximum: 10 },
    prefix_hoge: true    

  validates :email,
    presence: true
    email_validation: true

とはいえ、毎回カスタムバリデータを使うのではなく、それぞれのメリットデメリットを見極めて使うと良さそうです。

Xcode で info.plist のプロパティを追加する

プロパティの追加方法がわかりづらかったからメモ。

f:id:komaji504:20160508033247p:plain

上記の手順をふむとプロパティが追加されるので、以下のようにプロパティ名や型を目的の値に変更して完了です。

f:id:komaji504:20160508040415p:plain

ちなみに、、、

③で一番下のプロパティと書きましたが、どのプロパティでも構いません。
当該プロパティの下の欄にプロパティが追加されます。

また、 プロパティ横の ▶︎ が ▼ の状態で + をクリックすると、 当該プロパティの子プロパティとして追加されます。

Rails の Time と DateTime

日時を扱うクラスには DateTime と Time の二つがあり、どちらを使えば良いのかを知ることもなく過ごしてきてしまっていたのですが、ざっくりと知ることができたのでメモ。

基本的には Time ( ActiveSupport::TimeWithZone ) を使う

Rails 環境においては、特に理由がないときは Time クラスを使うと良いそうです。
正確に言うと、ActiveSupport::TimeWithZone クラスです。

現在時刻を取得する場合で言うと、DateTime.now ではなく、 Time.now でもなく Time.current を用いるのが良いです。

Time.current を用いることで、Time クラスではなく ActiveSupport::TimeWithZone クラスの時刻を取得することができるからです。 (内部的には Time.zone.now が実行されています。)

なんで?

ActiveSupport::TimeWithZone クラスは application.rb のタイムゾーンを使い、また、DB のタイムスタンプは Time.current で更新されます。 そのため、設定したタイムゾーンが反映されていないぞ!ということがなくなり、DBとも統一されるので、安心感もあります。

これらのことから、 ActiveSupport::TimeWithZone クラスの時刻を取得することができる Time クラスを使うと良いとのことでした。

ローカル環境の Ruboty を slack で動かす

ローカル環境の Ruboty を slack で動かしてみたときのメモです。

Ruboty の使い方は、先日投稿したエントリにざっくり書いてあります。

komaji504.hateblo.jp

slack team を用意する

作っていない場合はこちらから。 Slack: Be less busy

slack 用アダプタのインストール

Gemfile に gem 'ruboty-slack_rtm' を追記して bundle install します。

bot アカウントの追加

Bots | Slack

こちらから、 Ruboty を動かしたい team に Bots をインストールします。

Username を聞かれるので、好きな名前を入力して Add bot integration をクリック。 これが bot アカウントの名前になります。後から変更することもできます。

クリックした後の画面で表示される Integration Settings の API Token をメモしておきます。

slack を確認すると bot アカウントが追加されていると思います。

環境変数の設定

ターミナルで環境変数 SLACK_TOKEN を設定します。 この値を先ほどメモした API Token にします。

export=<API Token>

以上で準備完了です。

slack で動かしてみる

bundle exec ruboty
 

で Ruboty を起動します。 アダプタが設定されているので、 シェル上では何も表示されません。

slack を確認すると、 先ほど追加した bot アカウントがオンラインになっています。 この状態で、 bot アカウントのいる CHANNEL でコマンドを入力すると正しく動作するはずです。

ruboty ping を入力してみると pong が返ってくると思います!