Uniqueness of users with devise and acts_as_tenant in rails 3
Asked Answered
V

4

7

I am using the acts_as_tenant gem to manage multi-tenancy, and I'm using devise to manage users.

I have only setup devise User model and Account model for tenants. I can create users against multiple tenants - this is all working fine EXCEPT when I attempt to create two users with the same email against different tenant ID's I get a uniqeness error. I am using the validates_uniqueness_to_tenant option as described.

User model

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable,
  # :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  attr_accessible :email, :password, :password_confirmation, :remember_me

  acts_as_tenant(:account)
  validates_uniqueness_to_tenant :email
end

Account model

class Account < ActiveRecord::Base
  attr_accessible :name
end

Application Controller

class ApplicationController < ActionController::Base
  set_current_tenant_by_subdomain(:account, :subdomain)
  protect_from_forgery
end

This looks like it should be working based on all documentation in acts_as_tenant, do I need to override something at the devise level instead?

EDIT: After some head-scratching and a bit of a break, the problem is I believe because by default Devise has added a unique index to the Email column. This obviously does not gel with what acts_as_tenant wants to do... I will try removing the index and see whether Devise pukes or not.

EDIT 2: OK, have officially given up on this for now. I have hand-rolled authentication for the main site and this is working properly with acts_as_tenant. I can only assume some incompatibility between acts_as_tenant and Devise at some layer - beyond me to find it at this stage.

Variation answered 18/7, 2012 at 10:3 Comment(0)
W
11

The only way to do this is by removing the validatable module from devise and run your own validations like so:

class User < ActiveRecord::Base
  acts_as_tenant :account
  attr_accessible :email, :password, :remember_me

  #remove :validatable
  devise :database_authenticatable, :registerable,
    :recoverable, :rememberable, :trackable

  #run own validations
  #I've omitted any emailformatting checks for clarity's sake.
  validates :email, 
    presence: true,
    uniqueness: { scope: :account_id, case_sensitive: false }
  validates :password,
    presence: true,
    length: { :in => 6..20 },
    :if => :password_required?

protected
  # copied from validatable module
  def password_required?
    !persisted? || !password.nil? || !password_confirmation.nil?
  end

end
Wherefrom answered 18/9, 2012 at 18:28 Comment(2)
Good answer - worth noting for anyone else t remember to remove the unique index on email that devise adds as well.Variation
FYI: AaT provides a scoped validator: validates_uniqueness_to_tenant :email. Also the email format can easily be done using validates_format_of :email, with: Devise.email_regexpYodel
P
0

I haven't tested it, but I wonder if changing the order might help acts_as_tenant do its thing before devise takes over.

class User < ActiveRecord::Base

  acts_as_tenant(:account)
  validates_uniqueness_to_tenant :email

  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable,
  # :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
     :recoverable, :rememberable, :trackable, :validatable

  attr_accessible :email, :password, :password_confirmation, :remember_me

end
Pound answered 1/8, 2012 at 16:7 Comment(1)
Sounds like you are on to something with the devise index. If it's any help, in my systems I allow users to bind to more than one tenant and then give them the ability to switch their current tenant. This may not work for your particular needs.Pound
P
0

Just came across this question. Sweam's solution is pretty good.

But I prefer to not override the default behaviour. So I came up with this solution:

validate :remove_old_email_validation_error
validates_uniqueness_of :email, :allow_blank => true, :if => :email_changed?, :scope => [:account_id]

private

def remove_old_email_validation_error
  errors.delete(:email)
end

We remove the default validation error for email, therefore ignoring the validation check, and we do our own validation again. What I have added is from the Validatable module, but i have added :scope to it.

Its important to keep the order. Add the above code after the devise command.

Pickaxe answered 1/7, 2013 at 21:41 Comment(0)
F
0

I resolved it as:

validate :remove_old_uniquess_email_error

private

  def remove_old_uniquess_email_error
   errors.delete(:email) if self.company_id.nil? && errors[:email].present? && errors[:email] == ["already taken"]
  end
Feudality answered 9/6, 2019 at 8:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.