Pow をアンインストール

だいぶ前に、マルチテナントの Rails アプリをテストするために Pow を導入した。

便利に使っていたけど、もう用済みになったのでアンインストール。

$ curl get.pow.cx/uninstall.sh | sh

Pow は Mac 以外で使えないのがもったいない。 Node ではなく Go で作られていたら Windows でも使えたりしたかもな。

環境や開発者ごとに異なる設定を記述するのに dotenv が便利だった

Rails アプリ開発中、メール送信機能をテストするには GmailSMTP サーバーを使うのが手っ取り早い。

その際、ActionMailer の設定に Gmail のアカウントとパスワードを書く必要があるけど、 開発者ごとに違うし、そもそもアカウントとパスワードをソースコードに直接書きたくない。

そういった環境や開発者ごとに異なる情報を記述するために、 dotenv(dotenv-rails) を導入してみた。

Gemfile に

gem "dotenv-rails", groups: [:development, :test]

を追加して bundle install

プロジェクトのルートディレクトリに .env ファイルを作成し、環境変数にセットしたい情報を記述する。

GMAIL_ADDRESS="your-address@gmail.com"
GMAIL_PASSWORD="your-password"

すると、config/environment/development.rb の ActionMailer の設定が次のように書ける。

config.action_mailer.delivery_method = :smtp
config.action_mailer.raise_delivery_errors = true
config.action_mailer.smtp_settings = {
  enable_starttls_auto: true,
  address: "smtp.gmail.com",
  port: 587,
  domain: "smtp.gmail.com",
  authentication: "plain",
  user_name: ENV["GMAIL_ADDRESS"],
  password: ENV["GMAIL_PASSWORD"]
}

bin/rails server で開発サーバーを起動すれば、.env ファイルに書いた環境変数が自動で読み込まれて ENV にセットされる。

.env ファイルをリポジトリから除外するように .gitignore に追加しておけば、 Gmail のアカウントとパスワードをソースコードに記述せずに、 心置きなくメール送信をテストできる。

Rails 開発環境を構築するために Chef を使って PostgreSQL をインストールするメモ

Rails アプリの開発環境を Vagrant + Chef で構築していて、 PostgreSQL のインストールでつまづいたのでメモ。

まず Berkshelf で postgresql のクックブックをダウンロード。

echo cookbook "postgresql" >> Berksfile
berks vendor cookbooks

postgresql クックブックはそのまま使うと、どうしてもエンコーディングUTF-8 を指定してデータベースを作成できなかった。 対策として、postgresql をインポートするクックブックを新規作成する。

knife cookbook create postgresql_server_utf8

デフォルトのレシピを記述。

ENV["LANGUAGE"] = ENV["LANG"] = ENV["LC_ALL"] = "en_US.UTF-8"
include_recipe "postgresql::server"

環境変数をセットしているのがポイント。

あとは Vagrantfile に

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "hashicorp/precise64"

  config.vm.provision "chef_solo" do |chef|
    chef.cookbooks_path = ["./chef/cookbooks", "./chef/site-cookbooks"]

    chef.add_recipe "postgresql_server_utf8"
    chef.json = {
      "postgresql" => {
        "password" => {
          "postgres" => "postgres"
        },
        "initdb_locale" => "en_US.UTF-8",
        "config" => {
          "lc_messages" => "en_US.UTF-8",
          "lc_monetary" => "en_US.UTF-8",
          "lc_numeric" => "en_US.UTF-8",
          "lc_time" => "en_US.UTF-8"
        }
      }
    }
  end
end

を書いて、vagrant up/provision を実行すれば、 PostgreSQL をインストールできた。 エンコーディングUTF-8 を指定してデータベースを作成できるようにもなった。

Devise でのパスワード変更を自前で実装する

パスワード変更機能を、Devise が提供するコントローラーを使わずに、自前で実装したいときのためのメモ。

現在のパスワード・新しいパスワード・新しいパスワード(確認用) を入力して変更する場合、 update_with_password を使う。

current_user.update_with_password(
  password: "new_password",
  password_confirmation: "new_password",
  current_password: "old_password"
)

新しいパスワードで強制的に変更する場合、update を使う。 管理者がユーザーのパスワードを再発行するときなんかに使ったりする。

current_user.update(
  password: "new_password",
  password_confirmation: "new_password"
)

注意点としては、現在ログインしているユーザーのパスワードを変更すると、 直後ログアウトした状態になってしまうので、 再ログインする処理が必要。

class SettingsController < ApplicationController
  # ... 省略 ...

  def update
    respond_to do |format|
      if current_user.update_with_password(user_params)
        # パスワードを変更するとログアウトしてしまうので、再ログインが必要
       sign_in(current_user, bypass: true)
        format.html { redirect_to edit_setting_path }
      else
        format.html { render :edit }
      end
    end
  end
end

Draper と kaminari を一緒に使う

デフォルトの状態だと、kaminari が生やしたメソッドを Draper が delegate してくれない。

Draper::CollectionDecorator.delegate :current_page, :total_pages, :limit_value, :total_count

を config/initializers/draper.rb あたりに書いておく必要があった。

kaminari で Bootstrap のテーマを使う

$ bin/rails generate kaminari:views bootstrap

で Boostrap 用の kaminari のビューをダウンロードできる。 ただ、Bootstrap 2.0 のときのテーマなので、最新の 3.2 で使うには修正が必要。

app/views/kaminari/_pagenator.html.erb を次のように修正すれば使えた。

<%= paginator.render do -%>
  <ul class="pagination">
    <%= first_page_tag unless current_page.first? %>
    <%= prev_page_tag unless current_page.first? %>
    <% each_page do |page| -%>
      <% if page.left_outer? || page.right_outer? || page.inside_window? -%>
        <%= page_tag page %>
      <% elsif !page.was_truncated? -%>
        <%= gap_tag %>
      <% end -%>
    <% end -%>
    <%= next_page_tag unless current_page.last? %>
    <%= last_page_tag unless current_page.last? %>
  </ul>
<% end -%>

amatsuda/kaminari_themes には Bootstrap 3 対応のプルリクエストがいくつもあるけど、どれもマージされていない。まぁ、これくらいの修正で使えるからいいけど。

Devise でメールアドレスではなくユーザー名とパスワードでサインインできるようにする方法メモ

Devise ではメールアドレスとパスワードを使ってサインインするのがデフォルト。

メールアドレスではなくユーザー名、それとパスワードでサインインしたい場合はどうすればいいか? Devise が呼び出すモデルとコントローラーのメソッドを上書きするしかない。

やったことをメモしておく。

ユーザー名を保存する列を追加

すでに users テーブルを作っていたので、users テーブルに列を追加する。

$ rails generate migration AddUsernameToUsers

マイグレーションファイルの中身は次の通り。

class AddUsernameToUsers < ActiveRecord::Migration
  def change
    add_column :users, :username, :string, null: false, default: ""

    add_index :users, :username, unique: true
  end
end

あとはマイグレーションを実行。

認証のキーにユーザー名を使えるようにモデルを修正

ユーザー名を使ってサインインするのは User だけなので、 User クラスで認証に使うキーを指定する。

class User < ActiveRecord::Base
  # 認証で使うキーを指定
  devise :database_authenticatable, :trackable, :validatable,
    authentication_keys: [:username]

  # ユーザー名で検索
  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    if username = conditions.delete(:username)
      where(conditions).where(username: username).first
    else
      where(conditions).first
    end
  end

  # 登録時に email を不要にする
  def email_required?
    false
  end
  def email_changed?
    false
  end
end

ユーザー名を認証時のパラメータとして使えるようにコントローラーを修正

ユーザー名が Strong Parameters ではじかれないように対策が必要。

class ApplicationController < ActionController::Base
  before_filter :configure_permitted_parameters, if: :devise_controller?

  # ...省略...

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:subdomain, :username, :email, :password, :password_confirmation) }
    devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:subdomain, :username, :email, :password, :remember_me) }
  end
end

ユーザー名を入力できるようにカスタムビューを作成

ユーザー名とパスワードでサインインするのは User だけなので、 異なるビューを使えるように initializers/devise.rb で設定する。

Devise.setup do |config|
  # ...省略...

  config.scoped_views = true
end

ビューを生成。

$ rails generate devise:view users

app/views/users/sessions/new.html.erb を修正し、ユーザー名とパスワードを入力できるようにする。

<%= form_for(resource,
             as: resource_name,
             url: session_path(resource_name),
             html: { class: "form-signin", role: "form" }) do |f| %>
  <h2 class="form-signin-heading">
    サインイン
  </h2>

  <%= f.text_field :username,
    class: "form-control",
    placeholder: "ユーザー名" %>

  <%= f.password_field :password,
    autocomplete: "off",
    class: "form-control",
    placeholder: "パスワード" %>

  <%= f.submit "サインイン",
    data: { disable_with: "サインイン中..." },
    class: "btn btn-lg btn-primary btn-block" %>
<% end %>

なお、今回作っているアプリは「ユーザー登録を行うのは管理者ユーザーのみ」という仕様だったので、 ユーザー名とパスワードでのサインアップは省いた。