ハイパーニートプログラマーへの道

頑張ったり頑張らなかったり

【Ruby on Rails】sorceryを使用したログインで、ユーザーネーム・emailのどちらでもログインできるようにする

Deviseでの方法はいくらでもあるのですけど、sorceryについてはなかなか見つからなかったもので。


Rails version 4.2.5
Ruby version 2.3.0-p0 (x86_64-linux)
sorcery (0.9.1)


前提:公式のこちらのチュートリアルに沿って、emailでのログインは可能になっていること。つまり途中から導入する場合ということになります。

マイグレーションファイルの編集

db/migrate/****_sorcery_core.rbにて

class SorceryCore < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :email,            :null => false
      t.string :username,         :null => false # これを追加
      t.string :crypted_password
      t.string :salt

      t.timestamps
    end

    add_index :users, :email, unique: true
    add_index :users, :username, unique: true # これを追加
  end
end

emailの他に、今回はusernameというカラムを作ります。

そして、rake db:migrate:resetを行いました。もうすでにDBにユーザーを作っていたのですが、それにはusernameを設定していなかったので、リセットを行うことにしました。

設定ファイルの編集

config/initializers/sorcery.rbにて

  # --- user config ---
  config.user_config do |user|
    # -- core --
    # specify username attributes, for example: [:username, :email].
    # Default: `[:email]`
    #
    user.username_attribute_names = [:username, :email]

user.username_attribute_names:emailだけでなく、:usernameも追加します。

Strong Parametersへの追加

app/controllers/users_controller.rb

    def user_params
      params.require(:user).permit(:username, :email, :password, :password_confirmation)
    end

User modelにバリデーション追加

app/models/user.rb

  validates :email, :username, presence: true
.
.
  validates :username, uniqueness: true

:usernamepresence: trueuniqueness: trueのバリデーションをかけます。

ログインフォームの編集

ユーザーネーム用・email用のフォームを一つにまとめたい。
どうすればいいのかなあ、と思って試行錯誤したのですが、以下の方法にしました。

app/views/user_sessions/_form.html.erb
ログイン画面にてemail用のフィールドの代わりに、下記のコードに置き換えます。

  <div class="field">
    <%= label_tag :login %><br />
    <%= text_field_tag :login, nil, placeholder: 'username or email' %>
  </div>

params[:login]というのをコントローラーに渡すようにしておきます。ここにユーザーネーム、emailのどちらかが入ることになります。

f:id:noriyo_tcp:20160330204659p:plain

セッション用コントローラーの編集

app/controllers/user_sessions_contoller.rb

  def create
    # params[:login] is username or email
    if @user = login(params[:login], params[:password], params[:remember_me])
      redirect_back_or_to(:users, notice: 'Login successfully')
    else
      flash.now[:alert] = 'Login failed'
      render :new
    end
  end

デフォルトではloginメソッドの第1引数にparams[:email]を渡していましたが、ここではparams[:login]を渡します。

Processing by UserSessionsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"****", "login"=>"user1", "password"=>"[FILTERED]", "commit"=>"Login", "remember_me"=>"on"}
  User Load (1.9ms)  SELECT  "users".* FROM "users" WHERE ("users"."username" = 'user1' OR "users"."email" = 'user1')  ORDER BY "users"."id" ASC LIMIT 1

フィールドにuser1というユーザーネームでログインしたところをconsoleで見てみます。
usersテーブルのusernameemailのどちらかにuser1があるかどうか、検索してくれていますね。

Processing by UserSessionsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"****", "login"=>"test@example.com", "password"=>"[FILTERED]", "commit"=>"Login", "remember_me"=>"on"}
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE ("users"."username" = 'test@example.com' OR "users"."email" = 'test@example.com')  ORDER BY "users"."id" ASC LIMIT 1

今度はtest@example.comでログインしたところ。こちらでもOR検索してくれています。

その他

  • ユーザーネームも必須にしたので、ユーザー登録画面(app/views/users/_form.html.erb)に、username用のフォームを追加する。
  • ユーザーの一覧・詳細画面にもユーザーネームを表示する

ということをしました。


参考

github.com

dev.classmethod.jp

こちらの記事では、emailからユーザーネームでのログインに置き換える、ということをしています。


(追記)

うーん、記事を投稿してすぐに気がついたのですけど、もしユーザーネームのほうにふざけてuser@example.comなどと、emailと同じ形式で登録してしまうと、他のユーザーのemailとかぶってしまうことが無きにしもあらず、のような気がしました。
ユーザーネームには@, . (+も?)などは使えないようにバリデーションをかける必要があるかもです。

(さらに追記)

こう・・・かな?

app/models/user.rb

validates :username, format: { with: /\A\w+\z/ }

単語、数字、アンダースコアだけに制限すると。