使用PostgreSQL的模式和Rails创建多租户应用程序

时间:2010-05-06 16:37:51

标签: ruby-on-rails postgresql schema multi-tenant

东西我已经想通了

我正在学习如何在Rails中创建一个多租户应用程序,根据用于查看应用程序的域或子域来提供来自不同模式的数据。

我已经回答了一些问题:

  1. 如何让subdomain-fu与域名一起使用? Here's someone that asked the same question引导您this blog
  2. 什么数据库,以及如何构建?这是一个很棒的talk by Guy Naor,很好question about PostgreSQL and schemas
  3. 我已经知道我的模式都具有相同的结构。他们所拥有的数据会有所不同。那么,如何为所有模式运行迁移?以下是an answer
  4. 这三点涵盖了我需要知道的许多一般内容。但是,在接下来的步骤中,我似乎有许多实现方法。我希望有更好,更简单的方法。

    最后,我的问题

    当新用户注册时,我可以轻松创建架构。但是,加载其他模式已有的结构的最佳和最简单的方法是什么?以下是一些可能会让您更好的问题/方案。

    1. 我应该将它传递给 shell脚本,将公共架构转储到临时架构中,然后将其导回到我的主数据库(就像Guy Naor在他的视频中所说的那样)?这是一个quick summary/script I got from the helpful #postgres on freenode。虽然这可能会有效,但我必须在Rails之外做很多事情,这让我有点不舒服......这也让我想到了下一个问题。
    2. 有没有办法直接从Ruby on Rails 执行此操作?就像创建一个PostgreSQL模式一样,只需将Rails数据库模式(schema.rb - 我知道,这很令人困惑)加载到PostgreSQL模式中。
    3. 是否有包含这些内容的gem /插件?类似“create_pg_schema_and_load_rails_schema(the_new_schema_name)”的方法。如果没有,我可能会做一个,但我怀疑它对所有移动部件的测试结果如何(特别是如果我最终使用shell脚本来创建和管理新的PostgreSQL模式)。
    4. 谢谢,我希望不会太久!

3 个答案:

答案 0 :(得分:12)

2011年12月5日更新

感谢Brad Robertson和他的团队,有Apartment gem。它非常有用并且可以完成很多繁重的任务。

但是,如果你要修改架构,我强烈建议你知道它是如何工作的。熟悉Jerod Santo's walkthrough,这样你就会知道公寓宝石或多或少都在做什么。

更新2011年8月20日11:23 GMT + 8

有人创建了一个blog post并完成了整个过程。

2010年5月11日更新11:26 GMT + 8

从昨晚开始,我已经能够获得一种工作方法,可以创建一个新的模式并将schema.rb加载到其中。不确定我正在做的事情是否正确(到目前为止似乎工作正常),但至少距离更近了一步。如果有更好的方法请告诉我。


  module SchemaUtils
   def self.add_schema_to_path(schema)
    conn = ActiveRecord::Base.connection
    conn.execute "SET search_path TO #{schema}, #{conn.schema_search_path}"
   end

   def self.reset_search_path
    conn = ActiveRecord::Base.connection
    conn.execute "SET search_path TO #{conn.schema_search_path}"
   end

   def self.create_and_migrate_schema(schema_name)
    conn = ActiveRecord::Base.connection

    schemas = conn.select_values("select * from pg_namespace where nspname != 'information_schema' AND nspname NOT LIKE 'pg%'")

    if schemas.include?(schema_name)
     tables = conn.tables
     Rails.logger.info "#{schema_name} exists already with these tables #{tables.inspect}"
    else
     Rails.logger.info "About to create #{schema_name}"
     conn.execute "create schema #{schema_name}"
    end

    # Save the old search path so we can set it back at the end of this method
    old_search_path = conn.schema_search_path

    # Tried to set the search path like in the methods above (from Guy Naor)
    # [METHOD 1]: conn.execute "SET search_path TO #{schema_name}"
    # But the connection itself seems to remember the old search path.
    # When Rails executes a schema it first asks if the table it will load in already exists and if :force => true. 
    # If both true, it will drop the table and then load it. 
    # The problem is that in the METHOD 1 way of setting things, ActiveRecord::Base.connection.schema_search_path still returns $user,public.
    # That means that when Rails tries to load the schema, and asks if the tables exist, it searches for these tables in the public schema.
    # See line 655 in Rails 2.3.5 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
    # That's why I kept running into this error of the table existing when it didn't (in the newly created schema).
    # If used this way [METHOD 2], it works. ActiveRecord::Base.connection.schema_search_path returns the string we pass it.
    conn.schema_search_path = schema_name

    # Directly from databases.rake. 
    # In Rails 2.3.5 databases.rake can be found in railties/lib/tasks/databases.rake
    file = "#{Rails.root}/db/schema.rb"
    if File.exists?(file)
     Rails.logger.info "About to load the schema #{file}"
     load(file)
    else
     abort %{#{file} doesn't exist yet. It's possible that you just ran a migration!}
    end

    Rails.logger.info "About to set search path back to #{old_search_path}."
    conn.schema_search_path = old_search_path
   end
  end

答案 1 :(得分:3)

将第38行更改为:

conn.schema_search_path = "#{schema_name}, #{old_search_path}"

我假设postgres在加载schema.rb时尝试查找现有的表名,并且由于您已将search_path设置为仅包含新架构,因此它失败。当然,这假设你的数据库中仍然有公共模式。

希望有所帮助。

答案 2 :(得分:0)

  

是否有宝石/插件已经有这些东西?

pg_power提供此功能来在迁移中创建/删除PostgreSQL模式,如下所示:

def change
  # Create schema
  create_schema 'demography'

  # Create new table in specific schema
  create_table "countries", :schema => "demography" do |t|
    # columns goes here
  end

  # Drop schema
  drop_schema 'politics'
end

它还需要正确地将模式转储到schema.rb文件中。