You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Apartment+Devise创建租户失败,报PG事务错误求助

Hey there! Let's work through this multi-tenancy hiccup you're hitting with Apartment and Devise. That PG::InFailedSqlTransaction error usually means a prior SQL operation failed, leaving the transaction in a broken state—so when Apartment tries to run SET search_path TO "public", PostgreSQL blocks it until the transaction is resolved. Here's how to fix it:

1. Fix the Tenant & User Creation Order + Transaction Isolation

Devise's RegistrationsController#create wraps the user creation in a transaction by default. If you try to create a tenant inside that transaction and something fails, the whole transaction gets marked as invalid, triggering the error you see. Instead, split the logic: create the tenant first (outside Devise's transaction), switch to it, then create the user.

Update your RegistrationsController like this:

class RegistrationsController < Devise::RegistrationsController
  def create
    # First, create the tenant outside Devise's transaction
    tenant = Tenant.new(subdomain: params[:user][:subdomain])
    
    if tenant.save
      # Switch to the new tenant before creating the user
      Apartment::Tenant.switch!(tenant.subdomain)
      # Now let Devise handle user creation in the correct schema
      super
    else
      # Handle tenant creation failures cleanly
      flash[:alert] = "Couldn't create tenant: #{tenant.errors.full_messages.join(', ')}"
      redirect_to new_user_registration_path
    end
  end
end

2. Double-Check Your Apartment Configuration

Make sure your config/initializers/apartment.rb is set up to keep the Tenant model in the public schema (so you can access it without switching tenants):

Apartment.configure do |config|
  # Keep Tenant model in public schema
  config.excluded_models = ["Tenant"]
  # Dynamically fetch tenant subdomains
  config.tenant_names = -> { Tenant.pluck(:subdomain) }
  # Ensure public schema stays accessible
  config.persistent_schemas = %w(public)
end

3. Handle Transaction Rollbacks Explicitly

If a tenant creation does fail, make sure you don't leave the database in a broken transaction state. Add a rescue block to clean up:

def create_tenant(subdomain)
  begin
    Apartment::Tenant.create(subdomain)
  rescue ActiveRecord::StatementInvalid => e
    # Roll back the stuck transaction
    ActiveRecord::Base.connection.rollback_transaction
    # Re-raise or handle the error as needed
    raise "Tenant creation failed: #{e.message}"
  end
end

4. Verify Devise Callback Timing

Ensure you're not running any Devise callbacks that try to access tenant-specific data before switching to the correct schema. For example, avoid user validation logic that queries tenant tables until after you've switched to the new tenant.

The root issue here is that a failed operation left your PostgreSQL transaction in an aborted state, blocking Apartment's SET search_path command. By isolating tenant creation from Devise's transaction and ensuring clean error handling, you should be able to resolve this.

内容的提问来源于stack exchange,提问作者Jeremy Bray

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:02:44