multi-tenant rails app user-account relationship issue

575 Views Asked by At

My application main gems: rails 4.1.1, pg, acts_as_tenant, devise and activeadmin.

Application description: saas multi-tenant app with subdomains (not using postgresql schemas).
I have 2 models: Account and User with:

  • account belongs_to :owner a class_name for User
  • user belongs_to :account and also acts_as_tenant(:account)

Note: if using acts_as_tenant, I am not sure I need to declare belongs_to?

On public domain (www.myapp.dev) user can create a new account and choose own subdomain. This user has the status of ‘owner’ for this particular subdomain/account. After account creation, user is redirected to custom subdomain for sign in. Once signed in, owner can create/invite other users to join his account.

On account creation, owner_id is correctly saved to account record. In ActiveAdmin I can filter my accounts by owners.

The problem is the account_id is not saved in the user record. Also in ActiveAdmin, when I try to edit the users records to add the account, the data is not saved…

I am learning Rails so please be easy :)

My Account model :

class Account < ActiveRecord::Base
  RESTRICTED_SUBDOMAINS = %w(www)
  belongs_to :owner, class_name: 'User'
  validates :owner, presence: true
  validates :name, presence: true
  validates :subdomain, presence: true,
                   uniqueness: { case_sensitive: false },
                   format: { with: /\A[\w\-]+\Z/i, message: 'contains invalid characters' },
                   exclusion: { in: RESTRICTED_SUBDOMAINS, message: 'restricted' }
  accepts_nested_attributes_for :owner
  before_validation :downcase_subdomain

  def self.current_id=(id)
    Thread.current[:account_id] = id
  end

  def self.current_id
    Thread.current[:account_id]
  end

  private
  def downcase_subdomain
    self.subdomain = subdomain.try(:downcase)
  end
end

My User model :

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
     :recoverable, :rememberable, :trackable, :validatable
  belongs_to :account       
  acts_as_tenant(:account)
  validates :lastname, presence: true, allow_nil: false
  validates :firstname, presence: true, allow_nil: false
  validates :password, presence: true, allow_nil: false
  validates :email, presence: true, allow_nil: false #uniqueness: true,

  def to_s
    "#{firstname} #{lastname} (#{email})"
  end       
end

Application Controller :

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_filter :authenticate_user! #, :set_mailer_host
  before_filter :configure_permitted_parameters, if: :devise_controller?

  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:accept_invitation).concat([:firstname])
  end

  private
  def current_account
    @current_account ||= Account.find_by(subdomain: request.subdomain)
  end
  helper_method :current_account

  def after_sign_out_path_for(resource_or_scope)
    new_user_session_path
  end

  def after_invite_path_for(resource)
    users_path
  end
end

Account controller :

class AccountsController < ApplicationController
  skip_before_filter :authenticate_user!, only: [:new, :create]
  def new
    @account = Account.new
    @account.build_owner
  end

  def create
    @account = Account.new(account_params)
    if @account.valid?
      @account.save
      redirect_to new_user_session_url(subdomain: @account.subdomain)
    else
      render action: 'new'
    end
  end

private
  def account_params
    params.require(:account).permit(:subdomain, :name, owner_attributes: [:lastname, :firstname, :email, :password, :password_confirmation, :account_id])
  end
end

User controller :

class UsersController < ApplicationController
  def index
    @users = User.all
  end
end

And my db schema :

ActiveRecord::Schema.define(version: 20140619202210) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "accounts", force: true do |t|
    t.string   "name"
    t.string   "subdomain"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "owner_id"
  end

  create_table "active_admin_comments", force: true do |t|
    t.string   "namespace"
    t.text     "body"
    t.string   "resource_id",   null: false
    t.string   "resource_type", null: false
    t.integer  "author_id"
    t.string   "author_type"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "active_admin_comments", ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id", using: :btree
  add_index "active_admin_comments", ["namespace"], name: "index_active_admin_comments_on_namespace", using: :btree
  add_index "active_admin_comments", ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id", using: :btree

  create_table "admin_users", force: true do |t|
    t.string   "email",                  default: "", null: false
    t.string   "encrypted_password",     default: "", null: false
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0,  null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string   "current_sign_in_ip"
    t.string   "last_sign_in_ip"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "admin_users", ["email"], name: "index_admin_users_on_email", unique: true, using: :btree
  add_index "admin_users", ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true, using: :btree

  create_table "users", force: true do |t|
    t.string   "email",                  default: "", null: false
    t.string   "encrypted_password",     default: "", null: false
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0,  null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string   "current_sign_in_ip"
    t.string   "last_sign_in_ip"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "account_id"
    t.string   "lastname"
    t.string   "firstname"
  end

  add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
  add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree

end
1

There are 1 best solutions below

1
On

Try:

class Account < ActiveRecord::Base
  has_one :account
...

class User < ActiveRecord::Base
  acts_as_tenant(:account)
...

Without belongs_to for User model.